[
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"extends\": [\"next/core-web-vitals\", \"next/typescript\"]\n}\n"
  },
  {
    "path": ".example-env",
    "content": "NEXT_PUBLIC_APP_NAME = \"Prostore\"\nNEXT_PUBLIC_APP_DESCRIPTION = \"A modern ecommerce store built with Next.js\"\nNEXT_PUBLIC_SERVER_URL = \"http://localhost:3000\"\n\nDATABASE_URL=\"\"\n\nNEXTAUTH_SECRET=\"\"\nNEXTAUTH_URL=http://localhost:3000\nNEXTAUTH_URL_INTERNAL=http://localhost:3000\n\nPAYMENT_METHODS=\"PayPal, Stripe, CashOnDelivery\"\nDEFAULT_PAYMENT_METHOD=\"PayPal\"\n\nPAYPAL_API_URL=\"https://api-m.sandbox.paypal.com\"\nPAYPAL_CLIENT_ID=\"\"\nPAYPAL_APP_SECRET=\"\"\n\nUPLOADTHING_TOKEN=''\nUPLOADTHING_SECRET=\"\"\nUPLOADTHING_APPID=\"\"\n\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\"\"\nSTRIPE_SECRET_KEY=\"\"\n\nRESEND_API_KEY=\"\"\nSENDER_EMAIL=\"onboarding@resend.dev\""
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": ".prettierrc.yaml",
    "content": "printWidth: 80\ntabWidth: 2\nuseTabs: false\nsemi: true\nsingleQuote: true\nbracketSpacing: true\njsxBracketSameLine: false\njsxSingleQuote: true\ntrailingComma: es5\narrowFunctionParentheses: avoid\n"
  },
  {
    "path": "README.md",
    "content": "# Prostore\n\nA full featured Ecommerce website built with Next.js, TypeScript, PostgreSQL and Prisma.\n\n<img src=\"/public/images/screen.png\" alt=\"Next.js Ecommerce\" />\n\nThis project is from my **Next.js Ecommerce course**\n\n- Traversy Media: [https://www.traversymedia.com/nextjs-ecommerce](https://www.traversymedia.com/nextjs-ecommerce)\n- Udemy: [https://www.udemy.com/course/nextjs-ecommerce-course](https://www.udemy.com/course/nextjs-ecommerce-course)\n\n## Table of Contents\n\n<!--toc:start-->\n\n- [Features](#features)\n- [Usage](#usage)\n  - [Install Dependencies](#install-dependencies)\n  - [Environment Variables](#environment-variables)\n    - [PostgreSQL Database URL](#postgresql-database-url)\n    - [Next Auth Secret](#next-auth-secret)\n    - [PayPal Client ID and Secret](#paypal-client-id-and-secret)\n    - [Stripe Publishable and Secret Key](#stripe-publishable-and-secret-key)\n    - [Uploadthing Settings](#uploadthing-settings)\n    - [Resend API Key](#resend-api-key)\n  - [Run](#run)\n- [Prisma Studio](#prisma-studio)\n- [Seed Database](#seed-database)\n- [Demo](#demo)\n- [Bug Fixes And Course FAQ](#bug-fixes-and-course-faq)\n  - [Fix: Edge Function Middleware Limitations on Vercel](#fix-edge-function-middleware-limitations-on-vercel)\n  - [Bug: A newly logged in user can inherit the previous users cart](#bug-a-newly-logged-in-user-can-inherit-the-previous-users-cart)\n  - [Bug: Any user can see another users order](#bug-any-user-can-see-another-users-order)\n  - [Bug: Cart add and remove buttons share loading animation](#bug-cart-add-and-remove-buttons-share-loading-animation)\n  - [FAQ: Why are we using a JS click event in not-found](#faq-why-are-we-using-a-js-click-event-in-not-found)\n  - [Fix: TypeScript no-explicit-any in auth.ts](#fix-typescript-no-explicit-any-in-authts)\n- [TailwindCSS Update: Breaking Changes](#tailwindcss-update-breaking-changes)\n  - [Option 1: Stick with Tailwind v3 (Matches the Course)](#option-1-stick-with-tailwind-v3-matches-the-course)\n  - [Option 2: Use Tailwind v4 (Updated Code Available, this seems to be the smoothest option)](#option-2-use-tailwind-v4-updated-code-available-this-seems-to-be-the-smoothest-option)\n  - [Changes Needed for Tailwind v4:](#changes-needed-for-tailwind-v4)\n  - [Migrating from Tailwind v3 to v4 Mid-Course?](#migrating-from-tailwind-v3-to-v4-mid-course)\n  - [:link: Upgrade Guide](#link-upgrade-guide)\n- [License](#license)\n<!--toc:end-->\n\n## Features\n\n- Next Auth authentication\n- Admin area with stats & chart using Recharts\n- Order, product and user management\n- User area with profile and orders\n- Stripe API integration\n- PayPal integration\n- Cash on delivery option\n- Interactive checkout process\n- Featured products with banners\n- Multiple images using Uploadthing\n- Ratings & reviews system\n- Search form (customer & admin)\n- Sorting, filtering & pagination\n- Dark/Light mode\n- Much more\n\n## Usage\n\n### Install Dependencies\n\n```bash\nnpm install\n```\n\nNote: Some dependencies may have not yet been upadated to support React 19. If you get any errors about depencency compatability, run the following:\n\n```bash\nnpm install --legacy-peer-deps\n```\n\n### Environment Variables\n\nRename the `.example-env` file to `.env` and add the following\n\n#### PostgreSQL Database URL\n\nSign up for a free PostgreSQL database through Vercel. Log into Vercel and click on \"Storage\" and create a new Postgres database. Then add the URL.\n\n**Example:**\n\n```\nDATABASE_URL=\"postgresql://username:password@host:port/dbname\"\n```\n\n#### Next Auth Secret\n\nGenerate a secret with the following command and add it to your `.env`:\n\n```bash\nopenssl rand -base64 32\n```\n\n**Example:**\n\n```\nNEXTAUTH_SECRET=\"xmVpackzg9sdkEPzJsdGse3dskUY+4ni2quxvoK6Go=\"\n```\n\n#### PayPal Client ID and Secret\n\nCreate a PayPal developer account and create a new app to get the client ID and secret.\n\n**Example:**\n\n```\nPAYPAL_CLIENT_ID=\"AeFIdonfA_dW_ncys8G4LiECWBI9442IT_kRV15crlmMApC6zpb5Nsd7zlxj7UWJ5FRZtx\"\nPAYPAL_APP_SECRET=\"REdG53DEeX_ShoPawzM4vQHCYy0a554G3xXmzSxFCDcSofBBTq9VRqjs6xsNVBcbjqz--HiiGoiV\"\n```\n\n#### Stripe Publishable and Secret Key\n\nCreate a Stripe account and get the publishable and secret key.\n\n**Example:**\n\n```\nSTRIPE_SECRET_KEY=\"sk_test_51QIr0IG87GyTererxmXxEeqV6wuzbmC0TpkRzabxqy3P4BpzpzDqnQaC1lZhmYg6IfNarnvpnbjjw5dsBq4afd0FXkeDriR\"\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\"pk_test_51QIr0Ids7GyT6H7X6R5GoEA68lYDcbcC94VU0U02SMkrrrYZT2CgSMZ1h22udb5Rg1AuonXyjmAQZESLLj100W3VGVwze\"\n```\n\n#### Uploadthing Settings\n\nSign up for an account at https://uploadthing.com/ and get the token, secret and app ID.\n\n**Example:**\n\n```\nUPLOADTHING_TOKEN='tyJhcGlLZXkiOiJza19saXZlXzQ4YTE2ZjhiMDE5YmFiOgrgOWQ4MmYxMGQxZGU2NTM3YzlkZGI3YjNiZDk3MmRhNGZmNGMwMmJlOWI2Y2Q0N2UiLCJhcHBJZCI6InRyejZ2NHczNzUiLCJyZWdpb25zIjpbInNlYTEiXX0='\nUPLOADTHIUG_SECRET='gg'\nUPLOADTHING_APPID='trz6vd475'\n```\n\n#### Resend API Key\n\nSign up for an account at https://resend.io/ and get the API key.\n\n**Example:**\n\n```\nRESEND_API_KEY=\"re_ZnhUfrjR_QD2cDqdee3iYCrkfvPYFCYiXm\"\n```\n\n### Run\n\n```bash\n\n# Run in development mode\nnpm run dev\n\n# Build for production\nnpm run build\n\n# Run in production mode\nnpm start\n\n# Export static site\nnpm run export\n```\n\nOpen [http://localhost:3000](http://localhost:3000) with your browser to see the result.\n\n## Prisma Studio\n\nTo open Prisma Studio, run the following command:\n\n```bash\nnpx prisma studio\n```\n\n## Seed Database\n\nTo seed the database with sample data, run the following command:\n\n```bash\nnpx tsx ./db/seed\n```\n\n## Demo\n\nI am not sure how long I will have this demo up but you can view it here:\n\n[ https://prostore-one.vercel.app/ ](https://prostore-one.vercel.app/)\n\n## Bug Fixes And Course FAQ\n\n### Fix: Edge Function Middleware Limitations on Vercel\n\nAfter deploying your app you may be getting a build error along the lines of:\n\n> The Edge Function \"middleware size is 1.03 MB and your plan size limit is 1MB\n\nFor the solution to resolve this please see Brads [Gist here](https://gist.github.com/bradtraversy/16e3c89b9b25bc79cf86f5f36e14e83d)\n\nThere is also a new lesson added for this fix at the end of the course -\n**Vercel Hobby Tier Fix**\n\n### Bug: A newly logged in user can inherit the previous users cart\n\nIf a logged in user adds items to their cart and logs out then a different user\nlogs in on the same machine, they will inherit the first users cart.\n\nTo fix this we can delete the current users **Cart** from the database in our **lib/actions/user.actions.ts** `signOutUser` action.\n\n> Changes can be seen in [lib/actions/user.actions.ts](https://github.com/bradtraversy/prostore/blob/a498d4362d1485b2bd3152124cb5c3a75f8fdd70/lib/actions/user.actions.ts#L45)\n\n### Bug: Any user can see another users order\n\nIf a user knows the `Order.id` of another users order it is possible for them to\nvisit **/order/<Order.id>** and see that other users order. This isn't likely to\nhappen in reality but should be something we protect against by redirecting the\nuser to our **/unauthorized** page if they are not the owner of the order.\n\nIn **app/(root)/order/[id]/page.tsx** we can import the `redirect` function from Next:\n\n```ts\nimport { notFound, redirect } from 'next/navigation';\n```\n\nThen check if the user is the owner of the order and redirect them if not:\n\n```ts\n// Redirect the user if they don't own the order\nif (order.userId !== session?.user.id && session?.user.role !== 'admin') {\n  return redirect('/unauthorized');\n}\n```\n\n> Changes can be seen in [app/(root)/order/[id]/page.tsx](<https://github.com/bradtraversy/prostore/blob/main/app/(root)/order/%5Bid%5D/page.tsx>)\n\n### Bug: Cart add and remove buttons share loading animation\n\nOn our **/cart** page you may notice that when you increment or decrement the\nquantity of an item in the cart, then the loader shows for all buttons after we\nclick. This is because all the buttons use the same **pending** state from our\nuse of `useTransition` in our [app/(root)/cart/cart-table.tsx](<https://github.com/bradtraversy/prostore/blob/main/app/(root)/cart/cart-table.tsx>)\n\nWe can solve this by breaking out the Buttons into their own `AddButton` and\n`RemoveButton` components, each using their own `useTransition` and so having\ntheir own **pending** state.\n\nYou can if you wish move these components to their own files/modules but for\nease of following along they can be seen in the same file.\n\n> Changes can be seen in [app/(root)/cart/cart-table.tsx](<https://github.com/bradtraversy/prostore/blob/main/app/(root)/cart/cart-table.tsx>)\n\n### FAQ: Why are we using a JS click event in not-found\n\nIn our [app/not-found.tsx](https://github.com/bradtraversy/prostore/blob/main/app/not-found.tsx) we currently have:\n\n```tsx\n<Button\n  variant='outline'\n  className='mt-4 ml-2'\n  onClick={() => (window.location.href = '/')}\n>\n  Back To Home\n</Button>\n```\n\nSo we navigate the user back to the home page with a JavaScript click event,\nbut this should really be a `<a />` (link) instead.\n\nSo we can change the code to:\n\n```tsx\n<Button variant='outline' className='mt-4 ml-2' asChild>\n  <Link href='/'>Back To Home</Link>\n</Button>\n```\n\n> Changes can be seen in [app/not-found.tsx](https://github.com/bradtraversy/prostore/blob/main/app/not-found.tsx)\n\n### Fix: TypeScript no-explicit-any in auth.ts\n\nYou may be seeing warnings from TS in your **auth.ts** and **auth.config.ts**\nabout using the `any` Type.\n\nNormally the Types are inferred from NextAuth, and you don't need to do anything.  \nHere however it's `any` because we added in other properties to the `JWT`, `User` and the `Session` Types, namely **role**, **sub** and **name**.\nSo because the callbacks no longer match the built in types, then TS defaults to `any`\nThe correct way to remedy it would be to tell TS about those additions by [ Augmenting ](https://next-auth.js.org/getting-started/typescript#module-augmentation) the **NextAuth** types.\n\nSo if you haven't already then you would need to create a **types/next-auth.d.ts** file with the following:\n\n```ts\nimport { DefaultSession } from 'next-auth';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport NextAuth from 'next-auth';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport { JWT } from 'next-auth/jwt';\n\ndeclare module 'next-auth/jwt' {\n  /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */\n  interface JWT {\n    sub: string;\n    role: string;\n    name: string;\n  }\n}\n\ndeclare module 'next-auth' {\n  /**\n   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context\n   */\n  interface Session {\n    user: {\n      role: string;\n    } & DefaultSession['user'];\n  }\n\n  interface User {\n    role: string;\n  }\n}\n```\n\nThis augments the built in types so TS will know about our modifications.\n\nYou can then remove the use of the `any` type in **auth.ts** and **auth.config.ts**.  \nYou will also need to define the `config` object directly in the `NextAuth`\nconstructor, rather than creating the config object first.\n\n> Changes can be seen in:\n\n- [auth.ts](https://github.com/bradtraversy/prostore/blob/main/auth.ts)\n- [auth.config.ts](https://github.com/bradtraversy/prostore/blob/main/auth.config.ts)\n- [types/next-auth.d.ts](https://github.com/bradtraversy/prostore/blob/main/types/next-auth.d.ts)\n\n## TailwindCSS Update: Breaking Changes\n\nMany of you are running into issues following the course because **TailwindCSS recently had a major update**.  \nBy default, you'll install the latest version (**Tailwind v4**), but the course was recorded with **Tailwind v3**.\n\n### Option 1: Stick with Tailwind v3 (Matches the Course)\n\nIf you want to follow the course exactly, you should install **Tailwind v3** and refer to the v3 docs:  \n:link: **[Tailwind v3 Setup for Next.js](https://v3.tailwindcss.com/docs/guides/nextjs)**  \nMake sure your **tailwind.config.ts** matches [this file](https://github.com/bradtraversy/prostore/blob/main/tailwind.config.ts)\n\n### Option 2: Use Tailwind v4 (Updated Code Available, this seems to be the smoothest option)\n\nIf you'd rather use **Tailwind v4**, there is a **`tailwind4`** branch of this repository where you can grab the updated code:  \n:link: **[Updated Repo](https://github.com/bradtraversy/prostore/tree/tailwind4)**\n\n### Changes Needed for Tailwind v4:\n\n- **Delete** `tailwind.config.ts` (if it exists).\n- **Update** `globals.css` to match [this file](https://github.com/bradtraversy/prostore/blob/tailwind4/assets/styles/globals.css).\n- **Update** `postcss.config.mjs` to match [this file](https://github.com/bradtraversy/prostore/blob/tailwind4/postcss.config.mjs)\n- If you're using the latest Next.js, these should be the only changes required.\n- Make sure you have the `tailwindcss-animate` package installed - `npm i tailwindcss-animate`\n\n### Migrating from Tailwind v3 to v4 Mid-Course?\n\nIf you've already started the course with **Tailwind v3**, some **Radix UI components may break** due to class name changes.  \nThe easiest fix is to use Tailwind's migration tool:\n\n```sh\nnpx @tailwindcss/upgrade\n```\n\n### :link: Upgrade Guide\n\nIf you use the migration tool, you don't need to manually:\n\n- :white_check_mark: Update globals.css (the tool handles it).\n- :white_check_mark: Delete tailwind.config.ts.\n\nIf you run into issues, please post over on **Discord** or in the **Udemy Q&A**\nfor the course.\n\n## License\n\nMIT License\n\nCopyright (c) [2025] [Traversy Media]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall\n"
  },
  {
    "path": "app/(auth)/layout.tsx",
    "content": "export default function AuthLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return <div className='flex-center min-h-screen w-full'>{children}</div>;\n}\n"
  },
  {
    "path": "app/(auth)/sign-in/credentials-signin-form.tsx",
    "content": "'use client';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Label } from '@/components/ui/label';\nimport { signInDefaultValues } from '@/lib/constants';\nimport Link from 'next/link';\nimport { useActionState } from 'react';\nimport { useFormStatus } from 'react-dom';\nimport { signInWithCredentials } from '@/lib/actions/user.actions';\nimport { useSearchParams } from 'next/navigation';\n\nconst CredentialsSignInForm = () => {\n  const [data, action] = useActionState(signInWithCredentials, {\n    success: false,\n    message: '',\n  });\n\n  const searchParams = useSearchParams();\n  const callbackUrl = searchParams.get('callbackUrl') || '/';\n\n  const SignInButton = () => {\n    const { pending } = useFormStatus();\n\n    return (\n      <Button disabled={pending} className='w-full' variant='default'>\n        {pending ? 'Signing In...' : 'Sign In'}\n      </Button>\n    );\n  };\n\n  return (\n    <form action={action}>\n      <input type='hidden' name='callbackUrl' value={callbackUrl} />\n      <div className='space-y-6'>\n        <div>\n          <Label htmlFor='email'>Email</Label>\n          <Input\n            id='email'\n            name='email'\n            type='email'\n            required\n            autoComplete='email'\n            defaultValue={signInDefaultValues.email}\n          />\n        </div>\n        <div>\n          <Label htmlFor='password'>Password</Label>\n          <Input\n            id='password'\n            name='password'\n            type='password'\n            required\n            autoComplete='password'\n            defaultValue={signInDefaultValues.password}\n          />\n        </div>\n        <div>\n          <SignInButton />\n        </div>\n\n        {data && !data.success && (\n          <div className='text-center text-destructive'>{data.message}</div>\n        )}\n\n        <div className='text-sm text-center text-muted-foreground'>\n          Don&apos;t have an account?{' '}\n          <Link href='/sign-up' target='_self' className='link'>\n            Sign Up\n          </Link>\n        </div>\n      </div>\n    </form>\n  );\n};\n\nexport default CredentialsSignInForm;\n"
  },
  {
    "path": "app/(auth)/sign-in/page.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from '@/components/ui/card';\nimport { Metadata } from 'next';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport { APP_NAME } from '@/lib/constants';\nimport CredentialsSignInForm from './credentials-signin-form';\nimport { auth } from '@/auth';\nimport { redirect } from 'next/navigation';\n\nexport const metadata: Metadata = {\n  title: 'Sign In',\n};\n\nconst SignInPage = async (props: {\n  searchParams: Promise<{\n    callbackUrl: string;\n  }>;\n}) => {\n  const { callbackUrl } = await props.searchParams;\n\n  const session = await auth();\n\n  if (session) {\n    return redirect(callbackUrl || '/');\n  }\n\n  return (\n    <div className='w-full max-w-md mx-auto'>\n      <Card>\n        <CardHeader className='space-y-4'>\n          <Link href='/' className='flex-center'>\n            <Image\n              src='/images/logo.svg'\n              width={100}\n              height={100}\n              alt={`${APP_NAME} logo`}\n              priority={true}\n            />\n          </Link>\n          <CardTitle className='text-center'>Sign In</CardTitle>\n          <CardDescription className='text-center'>\n            Sign in to your account\n          </CardDescription>\n        </CardHeader>\n        <CardContent className='space-y-4'>\n          <CredentialsSignInForm />\n        </CardContent>\n      </Card>\n    </div>\n  );\n};\n\nexport default SignInPage;\n"
  },
  {
    "path": "app/(auth)/sign-up/page.tsx",
    "content": "import {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from '@/components/ui/card';\nimport { Metadata } from 'next';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport { APP_NAME } from '@/lib/constants';\nimport { auth } from '@/auth';\nimport { redirect } from 'next/navigation';\nimport SignUpForm from './sign-up-form';\n\nexport const metadata: Metadata = {\n  title: 'Sign Up',\n};\n\nconst SignUpPage = async (props: {\n  searchParams: Promise<{\n    callbackUrl: string;\n  }>;\n}) => {\n  const { callbackUrl } = await props.searchParams;\n\n  const session = await auth();\n\n  if (session) {\n    return redirect(callbackUrl || '/');\n  }\n\n  return (\n    <div className='w-full max-w-md mx-auto'>\n      <Card>\n        <CardHeader className='space-y-4'>\n          <Link href='/' className='flex-center'>\n            <Image\n              src='/images/logo.svg'\n              width={100}\n              height={100}\n              alt={`${APP_NAME} logo`}\n              priority={true}\n            />\n          </Link>\n          <CardTitle className='text-center'>Create Account</CardTitle>\n          <CardDescription className='text-center'>\n            Enter your information below to sign up\n          </CardDescription>\n        </CardHeader>\n        <CardContent className='space-y-4'>\n          <SignUpForm />\n        </CardContent>\n      </Card>\n    </div>\n  );\n};\n\nexport default SignUpPage;\n"
  },
  {
    "path": "app/(auth)/sign-up/sign-up-form.tsx",
    "content": "'use client';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { Label } from '@/components/ui/label';\nimport { signUpDefaultValues } from '@/lib/constants';\nimport Link from 'next/link';\nimport { useActionState } from 'react';\nimport { useFormStatus } from 'react-dom';\nimport { signUpUser } from '@/lib/actions/user.actions';\nimport { useSearchParams } from 'next/navigation';\n\nconst SignUpForm = () => {\n  const [data, action] = useActionState(signUpUser, {\n    success: false,\n    message: '',\n  });\n\n  const searchParams = useSearchParams();\n  const callbackUrl = searchParams.get('callbackUrl') || '/';\n\n  const SignUpButton = () => {\n    const { pending } = useFormStatus();\n\n    return (\n      <Button disabled={pending} className='w-full' variant='default'>\n        {pending ? 'Submitting...' : 'Sign Up'}\n      </Button>\n    );\n  };\n\n  return (\n    <form action={action}>\n      <input type='hidden' name='callbackUrl' value={callbackUrl} />\n      <div className='space-y-6'>\n        <div>\n          <Label htmlFor='email'>Name</Label>\n          <Input\n            id='name'\n            name='name'\n            type='text'\n            autoComplete='name'\n            defaultValue={signUpDefaultValues.name}\n          />\n        </div>\n        <div>\n          <Label htmlFor='email'>Email</Label>\n          <Input\n            id='email'\n            name='email'\n            type='text'\n            autoComplete='email'\n            defaultValue={signUpDefaultValues.email}\n          />\n        </div>\n        <div>\n          <Label htmlFor='password'>Password</Label>\n          <Input\n            id='password'\n            name='password'\n            type='password'\n            required\n            autoComplete='password'\n            defaultValue={signUpDefaultValues.password}\n          />\n        </div>\n        <div>\n          <Label htmlFor='confirmPassword'>Confirm Password</Label>\n          <Input\n            id='confirmPassword'\n            name='confirmPassword'\n            type='password'\n            required\n            autoComplete='confirmPassword'\n            defaultValue={signUpDefaultValues.confirmPassword}\n          />\n        </div>\n        <div>\n          <SignUpButton />\n        </div>\n\n        {data && !data.success && (\n          <div className='text-center text-destructive'>{data.message}</div>\n        )}\n\n        <div className='text-sm text-center text-muted-foreground'>\n          Already have an account?{' '}\n          <Link href='/sign-in' target='_self' className='link'>\n            Sign In\n          </Link>\n        </div>\n      </div>\n    </form>\n  );\n};\n\nexport default SignUpForm;\n"
  },
  {
    "path": "app/(root)/cart/cart-table.tsx",
    "content": "'use client';\nimport { useRouter } from 'next/navigation';\nimport { useToast } from '@/hooks/use-toast';\nimport { useTransition } from 'react';\nimport { addItemToCart, removeItemFromCart } from '@/lib/actions/cart.actions';\nimport { ArrowRight, Loader, Minus, Plus } from 'lucide-react';\nimport { Cart, CartItem } from '@/types';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport {\n  Table,\n  TableBody,\n  TableHead,\n  TableHeader,\n  TableRow,\n  TableCell,\n} from '@/components/ui/table';\nimport { Button } from '@/components/ui/button';\nimport { Card, CardContent } from '@/components/ui/card';\nimport { formatCurrency } from '@/lib/utils';\n\n// NOTE: The code here has changed from the original course code so that the\n// Buttons no longer share the same state and show the loader independently from\n// other items in the cart\nfunction AddButton({ item }: { item: CartItem }) {\n  const { toast } = useToast();\n  const [isPending, startTransition] = useTransition();\n  return (\n    <Button\n      disabled={isPending}\n      variant='outline'\n      type='button'\n      onClick={() =>\n        startTransition(async () => {\n          const res = await addItemToCart(item);\n\n          if (!res.success) {\n            toast({\n              variant: 'destructive',\n              description: res.message,\n            });\n          }\n        })\n      }\n    >\n      {isPending ? (\n        <Loader className='w-4 h-4 animate-spin' />\n      ) : (\n        <Plus className='w-4 h-4' />\n      )}\n    </Button>\n  );\n}\n\nfunction RemoveButton({ item }: { item: CartItem }) {\n  const { toast } = useToast();\n  const [isPending, startTransition] = useTransition();\n  return (\n    <Button\n      disabled={isPending}\n      variant='outline'\n      type='button'\n      onClick={() =>\n        startTransition(async () => {\n          const res = await removeItemFromCart(item.productId);\n\n          if (!res.success) {\n            toast({\n              variant: 'destructive',\n              description: res.message,\n            });\n          }\n        })\n      }\n    >\n      {isPending ? (\n        <Loader className='w-4 h-4 animate-spin' />\n      ) : (\n        <Minus className='w-4 h-4' />\n      )}\n    </Button>\n  );\n}\n\nconst CartTable = ({ cart }: { cart?: Cart }) => {\n  const router = useRouter();\n  const [isPending, startTransition] = useTransition();\n\n  return (\n    <>\n      <h1 className='py-4 h2-bold'>Shopping Cart</h1>\n      {!cart || cart.items.length === 0 ? (\n        <div>\n          Cart is empty. <Link href='/'>Go Shopping</Link>\n        </div>\n      ) : (\n        <div className='grid md:grid-cols-4 md:gap-5'>\n          <div className='overflow-x-auto md:col-span-3'>\n            <Table>\n              <TableHeader>\n                <TableRow>\n                  <TableHead>Item</TableHead>\n                  <TableHead className='text-center'>Quantity</TableHead>\n                  <TableHead className='text-right'>Price</TableHead>\n                </TableRow>\n              </TableHeader>\n              <TableBody>\n                {cart.items.map((item) => (\n                  <TableRow key={item.slug}>\n                    <TableCell>\n                      <Link\n                        href={`/product/${item.slug}`}\n                        className='flex items-center'\n                      >\n                        <Image\n                          src={item.image}\n                          alt={item.name}\n                          width={50}\n                          height={50}\n                        />\n                        <span className='px-2'>{item.name}</span>\n                      </Link>\n                    </TableCell>\n                    <TableCell className='flex-center gap-2'>\n                      <RemoveButton item={item} />\n                      <span>{item.qty}</span>\n                      <AddButton item={item} />\n                    </TableCell>\n                    <TableCell className='text-right'>${item.price}</TableCell>\n                  </TableRow>\n                ))}\n              </TableBody>\n            </Table>\n          </div>\n\n          <Card>\n            <CardContent className='p-4 gap-4'>\n              <div className='pb-3 text-xl'>\n                Subtotal ({cart.items.reduce((a, c) => a + c.qty, 0)}):\n                <span className='font-bold'>\n                  {formatCurrency(cart.itemsPrice)}\n                </span>\n              </div>\n              <Button\n                className='w-full'\n                disabled={isPending}\n                onClick={() =>\n                  startTransition(() => router.push('/shipping-address'))\n                }\n              >\n                {isPending ? (\n                  <Loader className='w-4 h-4 animate-spin' />\n                ) : (\n                  <ArrowRight className='w-4 h-4' />\n                )}{' '}\n                Proceed to Checkout\n              </Button>\n            </CardContent>\n          </Card>\n        </div>\n      )}\n    </>\n  );\n};\n\nexport default CartTable;\n"
  },
  {
    "path": "app/(root)/cart/page.tsx",
    "content": "import CartTable from './cart-table';\nimport { getMyCart } from '@/lib/actions/cart.actions';\n\nexport const metadata = {\n  title: 'Shopping Cart',\n};\n\nconst CartPage = async () => {\n  const cart = await getMyCart();\n\n  return (\n    <>\n      <CartTable cart={cart} />\n    </>\n  );\n};\n\nexport default CartPage;\n"
  },
  {
    "path": "app/(root)/layout.tsx",
    "content": "import Header from '@/components/shared/header';\nimport Footer from '@/components/footer';\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <div className='flex h-screen flex-col'>\n      <Header />\n      <main className='flex-1 wrapper'>{children}</main>\n      <Footer />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(root)/order/[id]/order-details-table.tsx",
    "content": "'use client';\nimport { Badge } from '@/components/ui/badge';\nimport { Card, CardContent } from '@/components/ui/card';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport { Button } from '@/components/ui/button';\nimport { formatCurrency, formatDateTime, formatId } from '@/lib/utils';\nimport { Order } from '@/types';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport { useToast } from '@/hooks/use-toast';\nimport { useTransition } from 'react';\nimport {\n  PayPalButtons,\n  PayPalScriptProvider,\n  usePayPalScriptReducer,\n} from '@paypal/react-paypal-js';\nimport {\n  createPayPalOrder,\n  approvePayPalOrder,\n  updateOrderToPaidCOD,\n  deliverOrder,\n} from '@/lib/actions/order.actions';\nimport StripePayment from './stripe-payment';\n\nconst OrderDetailsTable = ({\n  order,\n  paypalClientId,\n  isAdmin,\n  stripeClientSecret,\n}: {\n  order: Omit<Order, 'paymentResult'>;\n  paypalClientId: string;\n  isAdmin: boolean;\n  stripeClientSecret: string | null;\n}) => {\n  const {\n    id,\n    shippingAddress,\n    orderitems,\n    itemsPrice,\n    shippingPrice,\n    taxPrice,\n    totalPrice,\n    paymentMethod,\n    isDelivered,\n    isPaid,\n    paidAt,\n    deliveredAt,\n  } = order;\n\n  const { toast } = useToast();\n\n  const PrintLoadingState = () => {\n    const [{ isPending, isRejected }] = usePayPalScriptReducer();\n    let status = '';\n\n    if (isPending) {\n      status = 'Loading PayPal...';\n    } else if (isRejected) {\n      status = 'Error Loading PayPal';\n    }\n    return status;\n  };\n\n  const handleCreatePayPalOrder = async () => {\n    const res = await createPayPalOrder(order.id);\n\n    if (!res.success) {\n      toast({\n        variant: 'destructive',\n        description: res.message,\n      });\n    }\n\n    return res.data;\n  };\n\n  const handleApprovePayPalOrder = async (data: { orderID: string }) => {\n    const res = await approvePayPalOrder(order.id, data);\n\n    toast({\n      variant: res.success ? 'default' : 'destructive',\n      description: res.message,\n    });\n  };\n\n  // Button to mark order as paid\n  const MarkAsPaidButton = () => {\n    const [isPending, startTransition] = useTransition();\n    const { toast } = useToast();\n\n    return (\n      <Button\n        type='button'\n        disabled={isPending}\n        onClick={() =>\n          startTransition(async () => {\n            const res = await updateOrderToPaidCOD(order.id);\n            toast({\n              variant: res.success ? 'default' : 'destructive',\n              description: res.message,\n            });\n          })\n        }\n      >\n        {isPending ? 'processing...' : 'Mark As Paid'}\n      </Button>\n    );\n  };\n\n  // Button to mark order as delivered\n  const MarkAsDeliveredButton = () => {\n    const [isPending, startTransition] = useTransition();\n    const { toast } = useToast();\n\n    return (\n      <Button\n        type='button'\n        disabled={isPending}\n        onClick={() =>\n          startTransition(async () => {\n            const res = await deliverOrder(order.id);\n            toast({\n              variant: res.success ? 'default' : 'destructive',\n              description: res.message,\n            });\n          })\n        }\n      >\n        {isPending ? 'processing...' : 'Mark As Delivered'}\n      </Button>\n    );\n  };\n\n  return (\n    <>\n      <h1 className='py-4 text-2xl'>Order {formatId(id)}</h1>\n      <div className='grid md:grid-cols-3 md:gap-5'>\n        <div className='col-span-2 space-4-y overlow-x-auto'>\n          <Card>\n            <CardContent className='p-4 gap-4'>\n              <h2 className='text-xl pb-4'>Payment Method</h2>\n              <p className='mb-2'>{paymentMethod}</p>\n              {isPaid ? (\n                <Badge variant='secondary'>\n                  Paid at {formatDateTime(paidAt!).dateTime}\n                </Badge>\n              ) : (\n                <Badge variant='destructive'>Not paid</Badge>\n              )}\n            </CardContent>\n          </Card>\n          <Card className='my-2'>\n            <CardContent className='p-4 gap-4'>\n              <h2 className='text-xl pb-4'>Shipping Address</h2>\n              <p>{shippingAddress.fullName}</p>\n              <p className='mb-2'>\n                {shippingAddress.streetAddress}, {shippingAddress.city}\n                {shippingAddress.postalCode}, {shippingAddress.country}\n              </p>\n              {isDelivered ? (\n                <Badge variant='secondary'>\n                  Delivered at {formatDateTime(deliveredAt!).dateTime}\n                </Badge>\n              ) : (\n                <Badge variant='destructive'>Not Delivered</Badge>\n              )}\n            </CardContent>\n          </Card>\n          <Card>\n            <CardContent className='p-4 gap-4'>\n              <h2 className='text-xl pb-4'>Order Items</h2>\n              <Table>\n                <TableHeader>\n                  <TableRow>\n                    <TableHead>Item</TableHead>\n                    <TableHead>Quantity</TableHead>\n                    <TableHead>Price</TableHead>\n                  </TableRow>\n                </TableHeader>\n                <TableBody>\n                  {orderitems.map((item) => (\n                    <TableRow key={item.slug}>\n                      <TableCell>\n                        <Link\n                          href={`/product/{item.slug}`}\n                          className='flex items-center'\n                        >\n                          <Image\n                            src={item.image}\n                            alt={item.name}\n                            width={50}\n                            height={50}\n                          />\n                          <span className='px-2'>{item.name}</span>\n                        </Link>\n                      </TableCell>\n                      <TableCell>\n                        <span className='px-2'>{item.qty}</span>\n                      </TableCell>\n                      <TableCell className='text-right'>\n                        ${item.price}\n                      </TableCell>\n                    </TableRow>\n                  ))}\n                </TableBody>\n              </Table>\n            </CardContent>\n          </Card>\n        </div>\n        <div>\n          <Card>\n            <CardContent className='p-4 gap-4 space-y-4'>\n              <div className='flex justify-between'>\n                <div>Items</div>\n                <div>{formatCurrency(itemsPrice)}</div>\n              </div>\n              <div className='flex justify-between'>\n                <div>Tax</div>\n                <div>{formatCurrency(taxPrice)}</div>\n              </div>\n              <div className='flex justify-between'>\n                <div>Shipping</div>\n                <div>{formatCurrency(shippingPrice)}</div>\n              </div>\n              <div className='flex justify-between'>\n                <div>Total</div>\n                <div>{formatCurrency(totalPrice)}</div>\n              </div>\n\n              {/* PayPal Payment */}\n              {!isPaid && paymentMethod === 'PayPal' && (\n                <div>\n                  <PayPalScriptProvider options={{ clientId: paypalClientId }}>\n                    <PrintLoadingState />\n                    <PayPalButtons\n                      createOrder={handleCreatePayPalOrder}\n                      onApprove={handleApprovePayPalOrder}\n                    />\n                  </PayPalScriptProvider>\n                </div>\n              )}\n\n              {/* Stripe Payment */}\n              {!isPaid && paymentMethod === 'Stripe' && stripeClientSecret && (\n                <StripePayment\n                  priceInCents={Number(order.totalPrice) * 100}\n                  orderId={order.id}\n                  clientSecret={stripeClientSecret}\n                />\n              )}\n\n              {/* Cash On Delivery */}\n              {isAdmin && !isPaid && paymentMethod === 'CashOnDelivery' && (\n                <MarkAsPaidButton />\n              )}\n              {isAdmin && isPaid && !isDelivered && <MarkAsDeliveredButton />}\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </>\n  );\n};\n\nexport default OrderDetailsTable;\n"
  },
  {
    "path": "app/(root)/order/[id]/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { getOrderById } from '@/lib/actions/order.actions';\nimport { notFound, redirect } from 'next/navigation';\nimport OrderDetailsTable from './order-details-table';\nimport { ShippingAddress } from '@/types';\nimport { auth } from '@/auth';\nimport Stripe from 'stripe';\n\nexport const metadata: Metadata = {\n  title: 'Order Details',\n};\n\nconst OrderDetailsPage = async (props: {\n  params: Promise<{\n    id: string;\n  }>;\n}) => {\n  const { id } = await props.params;\n\n  const order = await getOrderById(id);\n  if (!order) notFound();\n\n  const session = await auth();\n\n  // Redirect the user if they don't own the order\n  if (order.userId !== session?.user.id && session?.user.role !== 'admin') {\n    return redirect('/unauthorized');\n  }\n\n  let client_secret = null;\n\n  // Check if is not paid and using stripe\n  if (order.paymentMethod === 'Stripe' && !order.isPaid) {\n    // Init stripe instance\n    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string);\n    // Create payment intent\n    const paymentIntent = await stripe.paymentIntents.create({\n      amount: Math.round(Number(order.totalPrice) * 100),\n      currency: 'USD',\n      metadata: { orderId: order.id },\n    });\n    client_secret = paymentIntent.client_secret;\n  }\n\n  return (\n    <OrderDetailsTable\n      order={{\n        ...order,\n        shippingAddress: order.shippingAddress as ShippingAddress,\n      }}\n      stripeClientSecret={client_secret}\n      paypalClientId={process.env.PAYPAL_CLIENT_ID || 'sb'}\n      isAdmin={session?.user?.role === 'admin' || false}\n    />\n  );\n};\n\nexport default OrderDetailsPage;\n"
  },
  {
    "path": "app/(root)/order/[id]/stripe-payment-success/page.tsx",
    "content": "import { Button } from '@/components/ui/button';\nimport { getOrderById } from '@/lib/actions/order.actions';\nimport Link from 'next/link';\nimport { notFound, redirect } from 'next/navigation';\nimport Stripe from 'stripe';\n\nconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string);\n\nconst SuccessPage = async (props: {\n  params: Promise<{ id: string }>;\n  searchParams: Promise<{ payment_intent: string }>;\n}) => {\n  const { id } = await props.params;\n  const { payment_intent: paymentIntentId } = await props.searchParams;\n\n  // Fetch order\n  const order = await getOrderById(id);\n  if (!order) notFound();\n\n  // Retrieve payment intent\n  const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);\n\n  // Check if payment intent is valid\n  if (\n    paymentIntent.metadata.orderId == null ||\n    paymentIntent.metadata.orderId !== order.id.toString()\n  ) {\n    return notFound();\n  }\n\n  // Check if payment is successful\n  const isSuccess = paymentIntent.status === 'succeeded';\n\n  if (!isSuccess) return redirect(`/order/${id}`);\n\n  return (\n    <div className='max-w-4xl w-full mx-auto space-y-8'>\n      <div className='flex flex-col gap-6 items-center'>\n        <h1 className='h1-bold'>Thanks for your purchase</h1>\n        <div>We are processing your order.</div>\n        <Button asChild>\n          <Link href={`/order/${id}`}>View Order</Link>\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nexport default SuccessPage;\n"
  },
  {
    "path": "app/(root)/order/[id]/stripe-payment.tsx",
    "content": "import { FormEvent, useState } from 'react';\nimport { loadStripe } from '@stripe/stripe-js';\nimport {\n  Elements,\n  LinkAuthenticationElement,\n  PaymentElement,\n  useElements,\n  useStripe,\n} from '@stripe/react-stripe-js';\nimport { useTheme } from 'next-themes';\nimport { Button } from '@/components/ui/button';\nimport { formatCurrency } from '@/lib/utils';\nimport { SERVER_URL } from '@/lib/constants';\n\nconst StripePayment = ({\n  priceInCents,\n  orderId,\n  clientSecret,\n}: {\n  priceInCents: number;\n  orderId: string;\n  clientSecret: string;\n}) => {\n  const stripePromise = loadStripe(\n    process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string\n  );\n\n  const { theme, systemTheme } = useTheme();\n\n  // Stripe Form Component\n  const StripeForm = () => {\n    const stripe = useStripe();\n    const elements = useElements();\n\n    const [isLoading, setIsLoading] = useState(false);\n    const [errorMessage, setErrorMessage] = useState('');\n    const [email, setEmail] = useState('');\n\n    const handleSubmit = async (e: FormEvent) => {\n      e.preventDefault();\n\n      if (stripe == null || elements == null || email == null) return;\n\n      setIsLoading(true);\n\n      stripe\n        .confirmPayment({\n          elements,\n          confirmParams: {\n            return_url: `${SERVER_URL}/order/${orderId}/stripe-payment-success`,\n          },\n        })\n        .then(({ error }) => {\n          if (\n            error?.type === 'card_error' ||\n            error?.type === 'validation_error'\n          ) {\n            setErrorMessage(error?.message ?? 'An unknown error occurred');\n          } else if (error) {\n            setErrorMessage('An unknown error occurred');\n          }\n        })\n        .finally(() => setIsLoading(false));\n    };\n\n    return (\n      <form className='space-y-4' onSubmit={handleSubmit}>\n        <div className='text-xl'>Stripe Checkout</div>\n        {errorMessage && <div className='text-destructive'>{errorMessage}</div>}\n        <PaymentElement />\n        <div>\n          <LinkAuthenticationElement\n            onChange={(e) => setEmail(e.value.email)}\n          />\n        </div>\n        <Button\n          className='w-full'\n          size='lg'\n          disabled={stripe == null || elements == null || isLoading}\n        >\n          {isLoading\n            ? 'Purchasing...'\n            : `Purchase ${formatCurrency(priceInCents / 100)}`}\n        </Button>\n      </form>\n    );\n  };\n\n  return (\n    <Elements\n      options={{\n        clientSecret,\n        appearance: {\n          theme:\n            theme === 'dark'\n              ? 'night'\n              : theme === 'light'\n              ? 'stripe'\n              : systemTheme === 'light'\n              ? 'stripe'\n              : 'night',\n        },\n      }}\n      stripe={stripePromise}\n    >\n      <StripeForm />\n    </Elements>\n  );\n};\n\nexport default StripePayment;\n"
  },
  {
    "path": "app/(root)/page.tsx",
    "content": "import ProductList from '@/components/shared/product/product-list';\nimport {\n  getLatestProducts,\n  getFeaturedProducts,\n} from '@/lib/actions/product.actions';\nimport ProductCarousel from '@/components/shared/product/product-carousel';\nimport ViewAllProductsButton from '@/components/view-all-products-button';\nimport IconBoxes from '@/components/icon-boxes';\nimport DealCountdown from '@/components/deal-countdown';\n\nconst Homepage = async () => {\n  const latestProducts = await getLatestProducts();\n  const featuredProducts = await getFeaturedProducts();\n\n  return (\n    <>\n      {featuredProducts.length > 0 && (\n        <ProductCarousel data={featuredProducts} />\n      )}\n      <ProductList data={latestProducts} title='Newest Arrivals' limit={4} />\n      <ViewAllProductsButton />\n      <DealCountdown />\n      <IconBoxes />\n    </>\n  );\n};\n\nexport default Homepage;\n"
  },
  {
    "path": "app/(root)/payment-method/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { auth } from '@/auth';\nimport { getUserById } from '@/lib/actions/user.actions';\nimport PaymentMethodForm from './payment-method-form';\nimport CheckoutSteps from '@/components/shared/checkout-steps';\n\nexport const metadata: Metadata = {\n  title: 'Select Payment Method',\n};\n\nconst PaymentMethodPage = async () => {\n  const session = await auth();\n  const userId = session?.user?.id;\n\n  if (!userId) throw new Error('User not found');\n\n  const user = await getUserById(userId);\n\n  return (\n    <>\n      <CheckoutSteps current={2} />\n      <PaymentMethodForm preferredPaymentMethod={user.paymentMethod} />\n    </>\n  );\n};\n\nexport default PaymentMethodPage;\n"
  },
  {
    "path": "app/(root)/payment-method/payment-method-form.tsx",
    "content": "'use client';\nimport { useRouter } from 'next/navigation';\nimport { useToast } from '@/hooks/use-toast';\nimport { useTransition } from 'react';\nimport { paymentMethodSchema } from '@/lib/validators';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { DEFAULT_PAYMENT_METHOD, PAYMENT_METHODS } from '@/lib/constants';\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '@/components/ui/form';\nimport { Button } from '@/components/ui/button';\nimport { ArrowRight, Loader } from 'lucide-react';\nimport { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';\nimport { updateUserPaymentMethod } from '@/lib/actions/user.actions';\n\nconst PaymentMethodForm = ({\n  preferredPaymentMethod,\n}: {\n  preferredPaymentMethod: string | null;\n}) => {\n  const router = useRouter();\n  const { toast } = useToast();\n\n  const form = useForm<z.infer<typeof paymentMethodSchema>>({\n    resolver: zodResolver(paymentMethodSchema),\n    defaultValues: {\n      type: preferredPaymentMethod || DEFAULT_PAYMENT_METHOD,\n    },\n  });\n\n  const [isPending, startTransition] = useTransition();\n\n  const onSubmit = async (values: z.infer<typeof paymentMethodSchema>) => {\n    startTransition(async () => {\n      const res = await updateUserPaymentMethod(values);\n\n      if (!res.success) {\n        toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n        return;\n      }\n\n      router.push('/place-order');\n    });\n  };\n\n  return (\n    <>\n      <div className='max-w-md mx-auto space-y-4'>\n        <h1 className='h2-bold mt-4'>Payment Method</h1>\n        <p className='text-sm text-muted-foreground'>\n          Please select a payment method\n        </p>\n        <Form {...form}>\n          <form\n            method='post'\n            className='space-y-4'\n            onSubmit={form.handleSubmit(onSubmit)}\n          >\n            <div className='flex flex-col md:flex-row gap-5'>\n              <FormField\n                control={form.control}\n                name='type'\n                render={({ field }) => (\n                  <FormItem className='space-y-3'>\n                    <FormControl>\n                      <RadioGroup\n                        onValueChange={field.onChange}\n                        className='flex flex-col space-y-2'\n                      >\n                        {PAYMENT_METHODS.map((paymentMethod) => (\n                          <FormItem\n                            key={paymentMethod}\n                            className='flex items-center space-x-3 space-y-0'\n                          >\n                            <FormControl>\n                              <RadioGroupItem\n                                value={paymentMethod}\n                                checked={field.value === paymentMethod}\n                              />\n                            </FormControl>\n                            <FormLabel className='font-normal'>\n                              {paymentMethod}\n                            </FormLabel>\n                          </FormItem>\n                        ))}\n                      </RadioGroup>\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </div>\n\n            <div className='flex gap-2'>\n              <Button type='submit' disabled={isPending}>\n                {isPending ? (\n                  <Loader className='w-4 h-4 animate-spin' />\n                ) : (\n                  <ArrowRight className='w-4 h-4' />\n                )}{' '}\n                Continue\n              </Button>\n            </div>\n          </form>\n        </Form>\n      </div>\n    </>\n  );\n};\n\nexport default PaymentMethodForm;\n"
  },
  {
    "path": "app/(root)/place-order/page.tsx",
    "content": "import { auth } from '@/auth';\nimport { getMyCart } from '@/lib/actions/cart.actions';\nimport { getUserById } from '@/lib/actions/user.actions';\nimport { ShippingAddress } from '@/types';\nimport { Metadata } from 'next';\nimport { redirect } from 'next/navigation';\nimport CheckoutSteps from '@/components/shared/checkout-steps';\nimport { Card, CardContent } from '@/components/ui/card';\nimport Link from 'next/link';\nimport { Button } from '@/components/ui/button';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport Image from 'next/image';\nimport { formatCurrency } from '@/lib/utils';\nimport PlaceOrderForm from './place-order-form';\n\nexport const metadata: Metadata = {\n  title: 'Place Order',\n};\n\nconst PlaceOrderPage = async () => {\n  const cart = await getMyCart();\n  const session = await auth();\n  const userId = session?.user?.id;\n\n  if (!userId) throw new Error('User not found');\n\n  const user = await getUserById(userId);\n\n  if (!cart || cart.items.length === 0) redirect('/cart');\n  if (!user.address) redirect('/shipping-address');\n  if (!user.paymentMethod) redirect('/payment-method');\n\n  const userAddress = user.address as ShippingAddress;\n\n  return (\n    <>\n      <CheckoutSteps current={3} />\n      <h1 className='py-4 text-2xl'>Place Order</h1>\n      <div className='grid md:grid-cols-3 md:gap-5'>\n        <div className='md:col-span-2 overflow-x-auto space-y-4'>\n          <Card>\n            <CardContent className='p-4 gap-4'>\n              <h2 className='text-xl pb-4'>Shipping Address</h2>\n              <p>{userAddress.fullName}</p>\n              <p>\n                {userAddress.streetAddress}, {userAddress.city}{' '}\n                {userAddress.postalCode}, {userAddress.country}{' '}\n              </p>\n              <div className='mt-3'>\n                <Link href='/shipping-address'>\n                  <Button variant='outline'>Edit</Button>\n                </Link>\n              </div>\n            </CardContent>\n          </Card>\n\n          <Card>\n            <CardContent className='p-4 gap-4'>\n              <h2 className='text-xl pb-4'>Payment Method</h2>\n              <p>{user.paymentMethod}</p>\n              <div className='mt-3'>\n                <Link href='/payment-method'>\n                  <Button variant='outline'>Edit</Button>\n                </Link>\n              </div>\n            </CardContent>\n          </Card>\n\n          <Card>\n            <CardContent className='p-4 gap-4'>\n              <h2 className='text-xl pb-4'>Order Items</h2>\n              <Table>\n                <TableHeader>\n                  <TableRow>\n                    <TableHead>Item</TableHead>\n                    <TableHead>Quantity</TableHead>\n                    <TableHead>Price</TableHead>\n                  </TableRow>\n                </TableHeader>\n                <TableBody>\n                  {cart.items.map((item) => (\n                    <TableRow key={item.slug}>\n                      <TableCell>\n                        <Link\n                          href={`/product/{item.slug}`}\n                          className='flex items-center'\n                        >\n                          <Image\n                            src={item.image}\n                            alt={item.name}\n                            width={50}\n                            height={50}\n                          />\n                          <span className='px-2'>{item.name}</span>\n                        </Link>\n                      </TableCell>\n                      <TableCell>\n                        <span className='px-2'>{item.qty}</span>\n                      </TableCell>\n                      <TableCell className='text-right'>\n                        ${item.price}\n                      </TableCell>\n                    </TableRow>\n                  ))}\n                </TableBody>\n              </Table>\n            </CardContent>\n          </Card>\n        </div>\n        <div>\n          <Card>\n            <CardContent className='p-4 gap-4 space-y-4'>\n              <div className='flex justify-between'>\n                <div>Items</div>\n                <div>{formatCurrency(cart.itemsPrice)}</div>\n              </div>\n              <div className='flex justify-between'>\n                <div>Tax</div>\n                <div>{formatCurrency(cart.taxPrice)}</div>\n              </div>\n              <div className='flex justify-between'>\n                <div>Shipping</div>\n                <div>{formatCurrency(cart.shippingPrice)}</div>\n              </div>\n              <div className='flex justify-between'>\n                <div>Total</div>\n                <div>{formatCurrency(cart.totalPrice)}</div>\n              </div>\n              <PlaceOrderForm />\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </>\n  );\n};\n\nexport default PlaceOrderPage;\n"
  },
  {
    "path": "app/(root)/place-order/place-order-form.tsx",
    "content": "'use client';\n\nimport { useRouter } from 'next/navigation';\nimport { Check, Loader } from 'lucide-react';\nimport { Button } from '@/components/ui/button';\nimport { useFormStatus } from 'react-dom';\nimport { createOrder } from '@/lib/actions/order.actions';\n\nconst PlaceOrderForm = () => {\n  const router = useRouter();\n\n  const handleSubmit = async (event: React.FormEvent) => {\n    event.preventDefault();\n\n    const res = await createOrder();\n\n    if (res.redirectTo) {\n      router.push(res.redirectTo);\n    }\n  };\n\n  const PlaceOrderButton = () => {\n    const { pending } = useFormStatus();\n    return (\n      <Button disabled={pending} className='w-full'>\n        {pending ? (\n          <Loader className='w-4 h-4 animate-spin' />\n        ) : (\n          <Check className='w-4 h-4' />\n        )}{' '}\n        Place Order\n      </Button>\n    );\n  };\n\n  return (\n    <form onSubmit={handleSubmit} className='w-full'>\n      <PlaceOrderButton />\n    </form>\n  );\n};\n\nexport default PlaceOrderForm;\n"
  },
  {
    "path": "app/(root)/product/[slug]/page.tsx",
    "content": "import { Badge } from '@/components/ui/badge';\nimport { Card, CardContent } from '@/components/ui/card';\nimport { getProductBySlug } from '@/lib/actions/product.actions';\nimport { notFound } from 'next/navigation';\nimport ProductPrice from '@/components/shared/product/product-price';\nimport ProductImages from '@/components/shared/product/product-images';\nimport AddToCart from '@/components/shared/product/add-to-cart';\nimport { getMyCart } from '@/lib/actions/cart.actions';\nimport ReviewList from './review-list';\nimport { auth } from '@/auth';\nimport Rating from '@/components/shared/product/rating';\n\nconst ProductDetailsPage = async (props: {\n  params: Promise<{ slug: string }>;\n}) => {\n  const { slug } = await props.params;\n\n  const product = await getProductBySlug(slug);\n  if (!product) notFound();\n\n  const session = await auth();\n  const userId = session?.user?.id;\n\n  const cart = await getMyCart();\n\n  return (\n    <>\n      <section>\n        <div className='grid grid-cols-1 md:grid-cols-5'>\n          {/* Images Column */}\n          <div className='col-span-2'>\n            <ProductImages images={product.images} />\n          </div>\n          {/* Details Column */}\n          <div className='col-span-2 p-5'>\n            <div className='flex flex-col gap-6'>\n              <p>\n                {product.brand} {product.category}\n              </p>\n              <h1 className='h3-bold'>{product.name}</h1>\n              <Rating value={Number(product.rating)} />\n              <p>{product.numReviews} reviews</p>\n              <div className='flex flex-col sm:flex-row sm:items-center gap-3'>\n                <ProductPrice\n                  value={Number(product.price)}\n                  className='w-24 rounded-full bg-green-100 text-green-700 px-5 py-2'\n                />\n              </div>\n            </div>\n            <div className='mt-10'>\n              <p className='font-semibold'>Description</p>\n              <p>{product.description}</p>\n            </div>\n          </div>\n          {/* Action Column */}\n          <div>\n            <Card>\n              <CardContent className='p-4'>\n                <div className='mb-2 flex justify-between'>\n                  <div>Price</div>\n                  <div>\n                    <ProductPrice value={Number(product.price)} />\n                  </div>\n                </div>\n                <div className='mb-2 flex justify-between'>\n                  <div>Status</div>\n                  {product.stock > 0 ? (\n                    <Badge variant='outline'>In Stock</Badge>\n                  ) : (\n                    <Badge variant='destructive'>Out Of Stock</Badge>\n                  )}\n                </div>\n                {product.stock > 0 && (\n                  <div className='flex-center'>\n                    <AddToCart\n                      cart={cart}\n                      item={{\n                        productId: product.id,\n                        name: product.name,\n                        slug: product.slug,\n                        price: product.price,\n                        qty: 1,\n                        image: product.images![0],\n                      }}\n                    />\n                  </div>\n                )}\n              </CardContent>\n            </Card>\n          </div>\n        </div>\n      </section>\n      <section className='mt-10'>\n        <h2 className='h2-bold mb-5'>Customer Reviews</h2>\n        <ReviewList\n          userId={userId || ''}\n          productId={product.id}\n          productSlug={product.slug}\n        />\n      </section>\n    </>\n  );\n};\n\nexport default ProductDetailsPage;\n"
  },
  {
    "path": "app/(root)/product/[slug]/review-form.tsx",
    "content": "'use client';\n\nimport { Button } from '@/components/ui/button';\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from '@/components/ui/dialog';\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '@/components/ui/form';\nimport { Input } from '@/components/ui/input';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select';\nimport { Textarea } from '@/components/ui/textarea';\nimport { useToast } from '@/hooks/use-toast';\nimport { reviewFormDefaultValues } from '@/lib/constants';\nimport { insertReviewSchema } from '@/lib/validators';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { StarIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport { SubmitHandler, useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport {\n  createUpdateReview,\n  getReviewByProductId,\n} from '@/lib/actions/review.actions';\n\nconst ReviewForm = ({\n  userId,\n  productId,\n  onReviewSubmitted,\n}: {\n  userId: string;\n  productId: string;\n  onReviewSubmitted: () => void;\n}) => {\n  const [open, setOpen] = useState(false);\n\n  const { toast } = useToast();\n\n  const form = useForm<z.infer<typeof insertReviewSchema>>({\n    resolver: zodResolver(insertReviewSchema),\n    defaultValues: reviewFormDefaultValues,\n  });\n\n  // Open Form Handler\n  const handleOpenForm = async () => {\n    form.setValue('productId', productId);\n    form.setValue('userId', userId);\n\n    const review = await getReviewByProductId({ productId });\n\n    if (review) {\n      form.setValue('title', review.title);\n      form.setValue('description', review.description);\n      form.setValue('rating', review.rating);\n    }\n\n    setOpen(true);\n  };\n\n  // Submit Form Handler\n  const onSubmit: SubmitHandler<z.infer<typeof insertReviewSchema>> = async (\n    values\n  ) => {\n    const res = await createUpdateReview({ ...values, productId });\n\n    if (!res.success) {\n      return toast({\n        variant: 'destructive',\n        description: res.message,\n      });\n    }\n\n    setOpen(false);\n\n    onReviewSubmitted();\n\n    toast({\n      description: res.message,\n    });\n  };\n\n  return (\n    <Dialog open={open} onOpenChange={setOpen}>\n      <Button onClick={handleOpenForm} variant='default'>\n        Write a Review\n      </Button>\n      <DialogContent className='sm:max-w-[425px]'>\n        <Form {...form}>\n          <form method='post' onSubmit={form.handleSubmit(onSubmit)}>\n            <DialogHeader>\n              <DialogTitle>Write a Review</DialogTitle>\n              <DialogDescription>\n                Share your thoughts with other customers\n              </DialogDescription>\n            </DialogHeader>\n            <div className='grid gap-4 py-4'>\n              <FormField\n                control={form.control}\n                name='title'\n                render={({ field }) => (\n                  <FormItem>\n                    <FormLabel>Title</FormLabel>\n                    <FormControl>\n                      <Input placeholder='Enter title' {...field} />\n                    </FormControl>\n                  </FormItem>\n                )}\n              />\n              <FormField\n                control={form.control}\n                name='description'\n                render={({ field }) => {\n                  return (\n                    <FormItem>\n                      <FormLabel>Description</FormLabel>\n                      <FormControl>\n                        <Textarea placeholder='Enter description' {...field} />\n                      </FormControl>\n                    </FormItem>\n                  );\n                }}\n              />\n              <FormField\n                control={form.control}\n                name='rating'\n                render={({ field }) => {\n                  return (\n                    <FormItem>\n                      <FormLabel>Rating</FormLabel>\n                      <Select\n                        onValueChange={field.onChange}\n                        value={field.value.toString()}\n                      >\n                        <FormControl>\n                          <SelectTrigger>\n                            <SelectValue />\n                          </SelectTrigger>\n                        </FormControl>\n                        <SelectContent>\n                          {Array.from({ length: 5 }).map((_, index) => (\n                            <SelectItem\n                              key={index}\n                              value={(index + 1).toString()}\n                            >\n                              {index + 1}{' '}\n                              <StarIcon className='inline h-4 w-4' />\n                            </SelectItem>\n                          ))}\n                        </SelectContent>\n                      </Select>\n                      <FormMessage />\n                    </FormItem>\n                  );\n                }}\n              />\n            </div>\n            <DialogFooter>\n              <Button\n                type='submit'\n                size='lg'\n                className='w-full'\n                disabled={form.formState.isSubmitting}\n              >\n                {form.formState.isSubmitting ? 'Submitting...' : 'Submit'}\n              </Button>\n            </DialogFooter>\n          </form>\n        </Form>\n      </DialogContent>\n    </Dialog>\n  );\n};\n\nexport default ReviewForm;\n"
  },
  {
    "path": "app/(root)/product/[slug]/review-list.tsx",
    "content": "'use client';\n\nimport { useEffect } from 'react';\nimport { Review } from '@/types';\nimport Link from 'next/link';\nimport { useState } from 'react';\nimport ReviewForm from './review-form';\nimport { getReviews } from '@/lib/actions/review.actions';\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from '@/components/ui/card';\nimport { Calendar, User } from 'lucide-react';\nimport { formatDateTime } from '@/lib/utils';\nimport Rating from '@/components/shared/product/rating';\n\nconst ReviewList = ({\n  userId,\n  productId,\n  productSlug,\n}: {\n  userId: string;\n  productId: string;\n  productSlug: string;\n}) => {\n  const [reviews, setReviews] = useState<Review[]>([]);\n\n  useEffect(() => {\n    const loadReviews = async () => {\n      const res = await getReviews({ productId });\n      setReviews(res.data);\n    };\n\n    loadReviews();\n  }, [productId]);\n\n  // Reload reviews after created or updated\n  const reload = async () => {\n    const res = await getReviews({ productId });\n    setReviews([...res.data]);\n  };\n\n  return (\n    <div className='space-y-4'>\n      {reviews.length === 0 && <div>No reviews yet</div>}\n      {userId ? (\n        <ReviewForm\n          userId={userId}\n          productId={productId}\n          onReviewSubmitted={reload}\n        />\n      ) : (\n        <div>\n          Please\n          <Link\n            className='text-blue-700 px-2'\n            href={`/sign-in?callbackUrl=/product/${productSlug}`}\n          >\n            sign in\n          </Link>\n          to write a review\n        </div>\n      )}\n      <div className='flex flex-col gap-3'>\n        {reviews.map((review) => (\n          <Card key={review.id}>\n            <CardHeader>\n              <div className='flex-between'>\n                <CardTitle>{review.title}</CardTitle>\n              </div>\n              <CardDescription>{review.description}</CardDescription>\n            </CardHeader>\n            <CardContent>\n              <div className='flex space-x-4 text-sm text-muted-foreground'>\n                <Rating value={review.rating} />\n                <div className='flex items-center'>\n                  <User className='mr-1 h-3 w-3' />\n                  {review.user ? review.user.name : 'User'}\n                </div>\n                <div className='flex items-center'>\n                  <Calendar className='mr-1 h-3 w-3' />\n                  {formatDateTime(review.createdAt).dateTime}\n                </div>\n              </div>\n            </CardContent>\n          </Card>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default ReviewList;\n"
  },
  {
    "path": "app/(root)/search/page.tsx",
    "content": "import ProductCard from '@/components/shared/product/product-card';\nimport { Button } from '@/components/ui/button';\nimport {\n  getAllProducts,\n  getAllCategories,\n} from '@/lib/actions/product.actions';\nimport Link from 'next/link';\n\nconst prices = [\n  {\n    name: '$1 to $50',\n    value: '1-50',\n  },\n  {\n    name: '$51 to $100',\n    value: '51-100',\n  },\n  {\n    name: '$101 to $200',\n    value: '101-200',\n  },\n  {\n    name: '$201 to $500',\n    value: '201-500',\n  },\n  {\n    name: '$501 to $1000',\n    value: '501-1000',\n  },\n];\n\nconst ratings = [4, 3, 2, 1];\n\nconst sortOrders = ['newest', 'lowest', 'highest', 'rating'];\n\nexport async function generateMetadata(props: {\n  searchParams: Promise<{\n    q: string;\n    category: string;\n    price: string;\n    rating: string;\n  }>;\n}) {\n  const {\n    q = 'all',\n    category = 'all',\n    price = 'all',\n    rating = 'all',\n  } = await props.searchParams;\n\n  const isQuerySet = q && q !== 'all' && q.trim() !== '';\n  const isCategorySet =\n    category && category !== 'all' && category.trim() !== '';\n  const isPriceSet = price && price !== 'all' && price.trim() !== '';\n  const isRatingSet = rating && rating !== 'all' && rating.trim() !== '';\n\n  if (isQuerySet || isCategorySet || isPriceSet || isRatingSet) {\n    return {\n      title: `\n      Search ${isQuerySet ? q : ''} \n      ${isCategorySet ? `: Category ${category}` : ''}\n      ${isPriceSet ? `: Price ${price}` : ''}\n      ${isRatingSet ? `: Rating ${rating}` : ''}`,\n    };\n  } else {\n    return {\n      title: 'Search Products',\n    };\n  }\n}\n\nconst SearchPage = async (props: {\n  searchParams: Promise<{\n    q?: string;\n    category?: string;\n    price?: string;\n    rating?: string;\n    sort?: string;\n    page?: string;\n  }>;\n}) => {\n  const {\n    q = 'all',\n    category = 'all',\n    price = 'all',\n    rating = 'all',\n    sort = 'newest',\n    page = '1',\n  } = await props.searchParams;\n\n  // Construct filter url\n  const getFilterUrl = ({\n    c,\n    p,\n    s,\n    r,\n    pg,\n  }: {\n    c?: string;\n    p?: string;\n    s?: string;\n    r?: string;\n    pg?: string;\n  }) => {\n    const params = { q, category, price, rating, sort, page };\n\n    if (c) params.category = c;\n    if (p) params.price = p;\n    if (s) params.sort = s;\n    if (r) params.rating = r;\n    if (pg) params.page = pg;\n\n    return `/search?${new URLSearchParams(params).toString()}`;\n  };\n\n  const products = await getAllProducts({\n    query: q,\n    category,\n    price,\n    rating,\n    sort,\n    page: Number(page),\n  });\n\n  const categories = await getAllCategories();\n\n  return (\n    <div className='grid md:grid-cols-5 md:gap-5'>\n      <div className='filter-links'>\n        {/* Category Links */}\n        <div className='text-xl mb-2 mt-3'>Department</div>\n        <div>\n          <ul className='space-y-1'>\n            <li>\n              <Link\n                className={`${\n                  (category === 'all' || category === '') && 'font-bold'\n                }`}\n                href={getFilterUrl({ c: 'all' })}\n              >\n                Any\n              </Link>\n            </li>\n            {categories.map((x) => (\n              <li key={x.category}>\n                <Link\n                  className={`${category === x.category && 'font-bold'}`}\n                  href={getFilterUrl({ c: x.category })}\n                >\n                  {x.category}\n                </Link>\n              </li>\n            ))}\n          </ul>\n        </div>\n        {/* Price Links */}\n        <div className='text-xl mb-2 mt-8'>Price</div>\n        <div>\n          <ul className='space-y-1'>\n            <li>\n              <Link\n                className={`${price === 'all' && 'font-bold'}`}\n                href={getFilterUrl({ p: 'all' })}\n              >\n                Any\n              </Link>\n            </li>\n            {prices.map((p) => (\n              <li key={p.value}>\n                <Link\n                  className={`${price === p.value && 'font-bold'}`}\n                  href={getFilterUrl({ p: p.value })}\n                >\n                  {p.name}\n                </Link>\n              </li>\n            ))}\n          </ul>\n        </div>\n        {/* Rating Links */}\n        <div className='text-xl mb-2 mt-8'>Customer Ratings</div>\n        <div>\n          <ul className='space-y-1'>\n            <li>\n              <Link\n                className={`${rating === 'all' && 'font-bold'}`}\n                href={getFilterUrl({ r: 'all' })}\n              >\n                Any\n              </Link>\n            </li>\n            {ratings.map((r) => (\n              <li key={r}>\n                <Link\n                  className={`${rating === r.toString() && 'font-bold'}`}\n                  href={getFilterUrl({ r: `${r}` })}\n                >\n                  {`${r} stars & up`}\n                </Link>\n              </li>\n            ))}\n          </ul>\n        </div>\n      </div>\n      <div className='md:col-span-4 space-y-4'>\n        <div className='flex-between flex-col md:flex-row my-4'>\n          <div className='flex items-center'>\n            {q !== 'all' && q !== '' && 'Query: ' + q}\n            {category !== 'all' && category !== '' && 'Category: ' + category}\n            {price !== 'all' && ' Price: ' + price}\n            {rating !== 'all' && ' Rating: ' + rating + ' stars & up'}\n            &nbsp;\n            {(q !== 'all' && q !== '') ||\n            (category !== 'all' && category !== '') ||\n            rating !== 'all' ||\n            price !== 'all' ? (\n              <Button variant={'link'} asChild>\n                <Link href='/search'>Clear</Link>\n              </Button>\n            ) : null}\n          </div>\n          <div>\n            Sort by{' '}\n            {sortOrders.map((s) => (\n              <Link\n                key={s}\n                className={`mx-2 ${sort == s && 'font-bold'}`}\n                href={getFilterUrl({ s })}\n              >\n                {s}\n              </Link>\n            ))}\n          </div>\n        </div>\n        <div className='grid grid-cols-1 gap-4 md:grid-cols-3'>\n          {products.data.length === 0 && <div>No products found</div>}\n          {products.data.map((product) => (\n            <ProductCard key={product.id} product={product} />\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default SearchPage;\n"
  },
  {
    "path": "app/(root)/shipping-address/page.tsx",
    "content": "import { auth } from '@/auth';\nimport { getMyCart } from '@/lib/actions/cart.actions';\nimport { getUserById } from '@/lib/actions/user.actions';\nimport { Metadata } from 'next';\nimport { redirect } from 'next/navigation';\nimport { ShippingAddress } from '@/types';\nimport ShippingAddressForm from './shipping-address-form';\nimport CheckoutSteps from '@/components/shared/checkout-steps';\n\nexport const metadata: Metadata = {\n  title: 'Shipping Address',\n};\n\nconst ShippingAddressPage = async () => {\n  const cart = await getMyCart();\n\n  if (!cart || cart.items.length === 0) redirect('/cart');\n\n  const session = await auth();\n\n  const userId = session?.user?.id;\n\n  if (!userId) throw new Error('No user ID');\n\n  const user = await getUserById(userId);\n\n  return (\n    <>\n      <CheckoutSteps current={1} />\n      <ShippingAddressForm address={user.address as ShippingAddress} />\n    </>\n  );\n};\n\nexport default ShippingAddressPage;\n"
  },
  {
    "path": "app/(root)/shipping-address/shipping-address-form.tsx",
    "content": "'use client';\n\nimport { useRouter } from 'next/navigation';\nimport { useToast } from '@/hooks/use-toast';\nimport { useTransition } from 'react';\nimport { ShippingAddress } from '@/types';\nimport { shippingAddressSchema } from '@/lib/validators';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { ControllerRenderProps, useForm, SubmitHandler } from 'react-hook-form';\nimport { z } from 'zod';\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '@/components/ui/form';\nimport { Input } from '@/components/ui/input';\nimport { Button } from '@/components/ui/button';\nimport { ArrowRight, Loader } from 'lucide-react';\nimport { updateUserAddress } from '@/lib/actions/user.actions';\nimport { shippingAddressDefaultValues } from '@/lib/constants';\n\nconst ShippingAddressForm = ({ address }: { address: ShippingAddress }) => {\n  const router = useRouter();\n  const { toast } = useToast();\n\n  const form = useForm<z.infer<typeof shippingAddressSchema>>({\n    resolver: zodResolver(shippingAddressSchema),\n    defaultValues: address || shippingAddressDefaultValues,\n  });\n\n  const [isPending, startTransition] = useTransition();\n\n  const onSubmit: SubmitHandler<z.infer<typeof shippingAddressSchema>> = async (\n    values\n  ) => {\n    startTransition(async () => {\n      const res = await updateUserAddress(values);\n\n      if (!res.success) {\n        toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n        return;\n      }\n\n      router.push('/payment-method');\n    });\n  };\n\n  return (\n    <>\n      <div className='max-w-md mx-auto space-y-4'>\n        <h1 className='h2-bold mt-4'>Shipping Address</h1>\n        <p className='text-sm text-muted-foreground'>\n          Please enter and address to ship to\n        </p>\n        <Form {...form}>\n          <form\n            method='post'\n            className='space-y-4'\n            onSubmit={form.handleSubmit(onSubmit)}\n          >\n            <div className='flex flex-col md:flex-row gap-5'>\n              <FormField\n                control={form.control}\n                name='fullName'\n                render={({\n                  field,\n                }: {\n                  field: ControllerRenderProps<\n                    z.infer<typeof shippingAddressSchema>,\n                    'fullName'\n                  >;\n                }) => (\n                  <FormItem className='w-full'>\n                    <FormLabel>Full Name</FormLabel>\n                    <FormControl>\n                      <Input placeholder='Enter full name' {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </div>\n            <div className='flex flex-col md:flex-row gap-5'>\n              <FormField\n                control={form.control}\n                name='streetAddress'\n                render={({\n                  field,\n                }: {\n                  field: ControllerRenderProps<\n                    z.infer<typeof shippingAddressSchema>,\n                    'streetAddress'\n                  >;\n                }) => (\n                  <FormItem className='w-full'>\n                    <FormLabel>Address</FormLabel>\n                    <FormControl>\n                      <Input placeholder='Enter address' {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </div>\n            <div className='flex flex-col md:flex-row gap-5'>\n              <FormField\n                control={form.control}\n                name='city'\n                render={({\n                  field,\n                }: {\n                  field: ControllerRenderProps<\n                    z.infer<typeof shippingAddressSchema>,\n                    'city'\n                  >;\n                }) => (\n                  <FormItem className='w-full'>\n                    <FormLabel>City</FormLabel>\n                    <FormControl>\n                      <Input placeholder='Enter city' {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </div>\n            <div className='flex flex-col md:flex-row gap-5'>\n              <FormField\n                control={form.control}\n                name='postalCode'\n                render={({\n                  field,\n                }: {\n                  field: ControllerRenderProps<\n                    z.infer<typeof shippingAddressSchema>,\n                    'postalCode'\n                  >;\n                }) => (\n                  <FormItem className='w-full'>\n                    <FormLabel>Postal Code</FormLabel>\n                    <FormControl>\n                      <Input placeholder='Enter postal code' {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </div>\n            <div className='flex flex-col md:flex-row gap-5'>\n              <FormField\n                control={form.control}\n                name='country'\n                render={({\n                  field,\n                }: {\n                  field: ControllerRenderProps<\n                    z.infer<typeof shippingAddressSchema>,\n                    'country'\n                  >;\n                }) => (\n                  <FormItem className='w-full'>\n                    <FormLabel>Country</FormLabel>\n                    <FormControl>\n                      <Input placeholder='Enter country' {...field} />\n                    </FormControl>\n                    <FormMessage />\n                  </FormItem>\n                )}\n              />\n            </div>\n            <div className='flex gap-2'>\n              <Button type='submit' disabled={isPending}>\n                {isPending ? (\n                  <Loader className='w-4 h-4 animate-spin' />\n                ) : (\n                  <ArrowRight className='w-4 h-4' />\n                )}{' '}\n                Continue\n              </Button>\n            </div>\n          </form>\n        </Form>\n      </div>\n    </>\n  );\n};\n\nexport default ShippingAddressForm;\n"
  },
  {
    "path": "app/admin/layout.tsx",
    "content": "import { APP_NAME } from '@/lib/constants';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport Menu from '@/components/shared/header/menu';\nimport MainNav from './main-nav';\nimport AdminSearch from '@/components/admin/admin-search';\n\nexport default function AdminLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <>\n      <div className='flex flex-col'>\n        <div className='border-b container mx-auto'>\n          <div className='flex items-center h-16 px-4'>\n            <Link href='/' className='w-22'>\n              <Image\n                src='/images/logo.svg'\n                height={48}\n                width={48}\n                alt={APP_NAME}\n              />\n            </Link>\n            <MainNav className='mx-6' />\n            <div className='ml-auto items-center flex space-x-4'>\n              <AdminSearch />\n              <Menu />\n            </div>\n          </div>\n        </div>\n\n        <div className='flex-1 space-y-4 p-8 pt-6 container mx-auto'>\n          {children}\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "app/admin/main-nav.tsx",
    "content": "'use client';\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { cn } from '@/lib/utils';\nimport React from 'react';\n\nconst links = [\n  {\n    title: 'Overview',\n    href: '/admin/overview',\n  },\n  {\n    title: 'Products',\n    href: '/admin/products',\n  },\n  {\n    title: 'Orders',\n    href: '/admin/orders',\n  },\n  {\n    title: 'Users',\n    href: '/admin/users',\n  },\n];\n\nconst MainNav = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLElement>) => {\n  const pathname = usePathname();\n  return (\n    <nav\n      className={cn('flex items-center space-x-4 lg:space-x-6', className)}\n      {...props}\n    >\n      {links.map((item) => (\n        <Link\n          key={item.href}\n          href={item.href}\n          className={cn(\n            'text-sm font-medium transition-colors hover:text-primary',\n            pathname.includes(item.href) ? '' : 'text-muted-foreground'\n          )}\n        >\n          {item.title}\n        </Link>\n      ))}\n    </nav>\n  );\n};\n\nexport default MainNav;\n"
  },
  {
    "path": "app/admin/orders/page.tsx",
    "content": "import {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport { deleteOrder, getAllOrders } from '@/lib/actions/order.actions';\nimport { formatCurrency, formatDateTime, formatId } from '@/lib/utils';\nimport { Metadata } from 'next';\nimport { Button } from '@/components/ui/button';\nimport Link from 'next/link';\nimport Pagination from '@/components/shared/pagination';\nimport DeleteDialog from '@/components/shared/delete-dialog';\nimport { requireAdmin } from '@/lib/auth-guard';\n\nexport const metadata: Metadata = {\n  title: 'Admin Orders',\n};\n\nconst AdminOrdersPage = async (props: {\n  searchParams: Promise<{ page: string; query: string }>;\n}) => {\n  const { page = '1', query: searchText } = await props.searchParams;\n\n  await requireAdmin();\n\n  const orders = await getAllOrders({\n    page: Number(page),\n    query: searchText,\n  });\n\n  return (\n    <div className='space-y-2'>\n      <div className='flex items-center gap-3'>\n        <h1 className='h2-bold'>Orders</h1>\n        {searchText && (\n          <div>\n            Filtered by <i>&quot;{searchText}&quot;</i>{' '}\n            <Link href='/admin/orders'>\n              <Button variant='outline' size='sm'>\n                Remove Filter\n              </Button>\n            </Link>\n          </div>\n        )}\n      </div>\n      <div className='overflow-x-auto'>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>ID</TableHead>\n              <TableHead>DATE</TableHead>\n              <TableHead>BUYER</TableHead>\n              <TableHead>TOTAL</TableHead>\n              <TableHead>PAID</TableHead>\n              <TableHead>DELIVERED</TableHead>\n              <TableHead>ACTIONS</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {orders.data.map((order) => (\n              <TableRow key={order.id}>\n                <TableCell>{formatId(order.id)}</TableCell>\n                <TableCell>\n                  {formatDateTime(order.createdAt).dateTime}\n                </TableCell>\n                <TableCell>{order.user.name}</TableCell>\n                <TableCell>{formatCurrency(order.totalPrice)}</TableCell>\n                <TableCell>\n                  {order.isPaid && order.paidAt\n                    ? formatDateTime(order.paidAt).dateTime\n                    : 'Not Paid'}\n                </TableCell>\n                <TableCell>\n                  {order.isDelivered && order.deliveredAt\n                    ? formatDateTime(order.deliveredAt).dateTime\n                    : 'Not Delivered'}\n                </TableCell>\n                <TableCell>\n                  <Button asChild variant='outline' size='sm'>\n                    <Link href={`/order/${order.id}`}>Details</Link>\n                  </Button>\n                  <DeleteDialog id={order.id} action={deleteOrder} />\n                </TableCell>\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {orders.totalPages > 1 && (\n          <Pagination\n            page={Number(page) || 1}\n            totalPages={orders?.totalPages}\n          />\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default AdminOrdersPage;\n"
  },
  {
    "path": "app/admin/overview/charts.tsx",
    "content": "'use client';\nimport { BarChart, Bar, XAxis, YAxis, ResponsiveContainer } from 'recharts';\n\nconst Charts = ({\n  data: { salesData },\n}: {\n  data: { salesData: { month: string; totalSales: number }[] };\n}) => {\n  return (\n    <ResponsiveContainer width='100%' height={350}>\n      <BarChart data={salesData}>\n        <XAxis\n          dataKey='month'\n          stroke='#888888'\n          fontSize={12}\n          tickLine={false}\n          axisLine={false}\n        />\n        <YAxis\n          stroke='#888888'\n          fontSize={12}\n          tickLine={false}\n          axisLine={false}\n          tickFormatter={(value) => `$${value}`}\n        />\n        <Bar\n          dataKey='totalSales'\n          fill='currentColor'\n          radius={[4, 4, 0, 0]}\n          className='fill-primary'\n        />\n      </BarChart>\n    </ResponsiveContainer>\n  );\n};\n\nexport default Charts;\n"
  },
  {
    "path": "app/admin/overview/page.tsx",
    "content": "import {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';\nimport { getOrderSummary } from '@/lib/actions/order.actions';\nimport { formatCurrency, formatDateTime, formatNumber } from '@/lib/utils';\nimport { BadgeDollarSign, Barcode, CreditCard, Users } from 'lucide-react';\nimport { Metadata } from 'next';\nimport Link from 'next/link';\nimport Charts from './charts';\nimport { requireAdmin } from '@/lib/auth-guard';\n\nexport const metadata: Metadata = {\n  title: 'Admin Dashboard',\n};\n\nconst AdminOverviewPage = async () => {\n  await requireAdmin();\n\n  const summary = await getOrderSummary();\n\n  return (\n    <div className='space-y-2'>\n      <h1 className='h2-bold'>Dashboard</h1>\n      <div className='grid gap-4 md:grid-cols-2 lg:grid-cols-4'>\n        <Card>\n          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>\n            <CardTitle className='text-sm font-medium'>Total Revenue</CardTitle>\n            <BadgeDollarSign />\n          </CardHeader>\n          <CardContent>\n            <div className='text-2xl font-bold'>\n              {formatCurrency(\n                summary.totalSales._sum.totalPrice?.toString() || 0\n              )}\n            </div>\n          </CardContent>\n        </Card>\n        <Card>\n          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>\n            <CardTitle className='text-sm font-medium'>Sales</CardTitle>\n            <CreditCard />\n          </CardHeader>\n          <CardContent>\n            <div className='text-2xl font-bold'>\n              {formatNumber(summary.ordersCount)}\n            </div>\n          </CardContent>\n        </Card>\n        <Card>\n          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>\n            <CardTitle className='text-sm font-medium'>Customers</CardTitle>\n            <Users />\n          </CardHeader>\n          <CardContent>\n            <div className='text-2xl font-bold'>\n              {formatNumber(summary.usersCount)}\n            </div>\n          </CardContent>\n        </Card>\n        <Card>\n          <CardHeader className='flex flex-row items-center justify-between space-y-0 pb-2'>\n            <CardTitle className='text-sm font-medium'>Products</CardTitle>\n            <Barcode />\n          </CardHeader>\n          <CardContent>\n            <div className='text-2xl font-bold'>\n              {formatNumber(summary.productsCount)}\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n      <div className='grid gap-4 md:grid-cols-2 lg:grid-cols-7'>\n        <Card className='col-span-4'>\n          <CardHeader>\n            <CardTitle>Overview</CardTitle>\n          </CardHeader>\n          <CardContent>\n            <Charts\n              data={{\n                salesData: summary.salesData,\n              }}\n            />\n          </CardContent>\n        </Card>\n        <Card className='col-span-3'>\n          <CardHeader>\n            <CardTitle>Recent Sales</CardTitle>\n          </CardHeader>\n          <CardContent>\n            <Table>\n              <TableHeader>\n                <TableRow>\n                  <TableHead>BUYER</TableHead>\n                  <TableHead>DATE</TableHead>\n                  <TableHead>TOTAL</TableHead>\n                  <TableHead>ACTIONS</TableHead>\n                </TableRow>\n              </TableHeader>\n              <TableBody>\n                {summary.latestSales.map((order) => (\n                  <TableRow key={order.id}>\n                    <TableCell>\n                      {order?.user?.name ? order.user.name : 'Deleted User'}\n                    </TableCell>\n                    <TableCell>\n                      {formatDateTime(order.createdAt).dateOnly}\n                    </TableCell>\n                    <TableCell>{formatCurrency(order.totalPrice)}</TableCell>\n                    <TableCell>\n                      <Link href={`/order/${order.id}`}>\n                        <span className='px-2'>Details</span>\n                      </Link>\n                    </TableCell>\n                  </TableRow>\n                ))}\n              </TableBody>\n            </Table>\n          </CardContent>\n        </Card>\n      </div>\n    </div>\n  );\n};\n\nexport default AdminOverviewPage;\n"
  },
  {
    "path": "app/admin/products/[id]/page.tsx",
    "content": "import ProductForm from '@/components/admin/product-form';\nimport { getProductById } from '@/lib/actions/product.actions';\nimport { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { requireAdmin } from '@/lib/auth-guard';\n\nexport const metadata: Metadata = {\n  title: 'Update Product',\n};\n\nconst AdminProductUpdatePage = async (props: {\n  params: Promise<{\n    id: string;\n  }>;\n}) => {\n  await requireAdmin();\n\n  const { id } = await props.params;\n\n  const product = await getProductById(id);\n\n  if (!product) return notFound();\n\n  return (\n    <div className='space-y-8 max-w-5xl mx-auto'>\n      <h1 className='h2-bold'>Update Product</h1>\n\n      <ProductForm type='Update' product={product} productId={product.id} />\n    </div>\n  );\n};\n\nexport default AdminProductUpdatePage;\n"
  },
  {
    "path": "app/admin/products/create/page.tsx",
    "content": "import { Metadata } from 'next';\nimport ProductForm from '@/components/admin/product-form';\nimport { requireAdmin } from '@/lib/auth-guard';\nexport const metadata: Metadata = {\n  title: 'Create Product',\n};\n\nconst CreateProductPage = async () => {\n  await requireAdmin();\n  return (\n    <>\n      <h2 className='h2-bold'>Create Product</h2>\n      <div className='my-8'>\n        <ProductForm type='Create' />\n      </div>\n    </>\n  );\n};\n\nexport default CreateProductPage;\n"
  },
  {
    "path": "app/admin/products/page.tsx",
    "content": "import Link from 'next/link';\nimport { getAllProducts, deleteProduct } from '@/lib/actions/product.actions';\nimport { formatCurrency, formatId } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport Pagination from '@/components/shared/pagination';\nimport DeleteDialog from '@/components/shared/delete-dialog';\nimport { requireAdmin } from '@/lib/auth-guard';\n\nconst AdminProductsPage = async (props: {\n  searchParams: Promise<{\n    page: string;\n    query: string;\n    category: string;\n  }>;\n}) => {\n  await requireAdmin();\n\n  const searchParams = await props.searchParams;\n\n  const page = Number(searchParams.page) || 1;\n  const searchText = searchParams.query || '';\n  const category = searchParams.category || '';\n\n  const products = await getAllProducts({\n    query: searchText,\n    page,\n    category,\n  });\n\n  return (\n    <div className='space-y-2'>\n      <div className='flex-between'>\n        <div className='flex items-center gap-3'>\n          <h1 className='h2-bold'>Products</h1>\n          {searchText && (\n            <div>\n              Filtered by <i>&quot;{searchText}&quot;</i>{' '}\n              <Link href='/admin/products'>\n                <Button variant='outline' size='sm'>\n                  Remove Filter\n                </Button>\n              </Link>\n            </div>\n          )}\n        </div>\n        <Button asChild variant='default'>\n          <Link href='/admin/products/create'>Create Product</Link>\n        </Button>\n      </div>\n\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead>ID</TableHead>\n            <TableHead>NAME</TableHead>\n            <TableHead className='text-right'>PRICE</TableHead>\n            <TableHead>CATEGORY</TableHead>\n            <TableHead>STOCK</TableHead>\n            <TableHead>RATING</TableHead>\n            <TableHead className='w-[100px]'>ACTIONS</TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          {products.data.map((product) => (\n            <TableRow key={product.id}>\n              <TableCell>{formatId(product.id)}</TableCell>\n              <TableCell>{product.name}</TableCell>\n              <TableCell className='text-right'>\n                {formatCurrency(product.price)}\n              </TableCell>\n              <TableCell>{product.category}</TableCell>\n              <TableCell>{product.stock}</TableCell>\n              <TableCell>{product.rating}</TableCell>\n              <TableCell className='flex gap-1'>\n                <Button asChild variant='outline' size='sm'>\n                  <Link href={`/admin/products/${product.id}`}>Edit</Link>\n                </Button>\n                <DeleteDialog id={product.id} action={deleteProduct} />\n              </TableCell>\n            </TableRow>\n          ))}\n        </TableBody>\n      </Table>\n      {products.totalPages > 1 && (\n        <Pagination page={page} totalPages={products.totalPages} />\n      )}\n    </div>\n  );\n};\n\nexport default AdminProductsPage;\n"
  },
  {
    "path": "app/admin/users/[id]/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { getUserById } from '@/lib/actions/user.actions';\nimport UpdateUserForm from './update-user-form';\nimport { requireAdmin } from '@/lib/auth-guard';\n\nexport const metadata: Metadata = {\n  title: 'Update User',\n};\n\nconst AdminUserUpdatePage = async (props: {\n  params: Promise<{\n    id: string;\n  }>;\n}) => {\n  await requireAdmin();\n\n  const { id } = await props.params;\n\n  const user = await getUserById(id);\n\n  if (!user) notFound();\n\n  return (\n    <div className='space-y-8 max-w-lg mx-auto'>\n      <h1 className='h2-bold'>Update User</h1>\n      <UpdateUserForm user={user} />\n    </div>\n  );\n};\n\nexport default AdminUserUpdatePage;\n"
  },
  {
    "path": "app/admin/users/[id]/update-user-form.tsx",
    "content": "'use client';\n\nimport { Button } from '@/components/ui/button';\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '@/components/ui/form';\nimport { Input } from '@/components/ui/input';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select';\nimport { useToast } from '@/hooks/use-toast';\nimport { updateUser } from '@/lib/actions/user.actions';\nimport { USER_ROLES } from '@/lib/constants';\nimport { updateUserSchema } from '@/lib/validators';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useRouter } from 'next/navigation';\nimport { ControllerRenderProps, useForm } from 'react-hook-form';\nimport { z } from 'zod';\n\nconst UpdateUserForm = ({\n  user,\n}: {\n  user: z.infer<typeof updateUserSchema>;\n}) => {\n  const router = useRouter();\n  const { toast } = useToast();\n\n  const form = useForm<z.infer<typeof updateUserSchema>>({\n    resolver: zodResolver(updateUserSchema),\n    defaultValues: user,\n  });\n\n  const onSubmit = async (values: z.infer<typeof updateUserSchema>) => {\n    try {\n      const res = await updateUser({\n        ...values,\n        id: user.id,\n      });\n\n      if (!res.success) {\n        return toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n      }\n\n      toast({\n        description: res.message,\n      });\n      form.reset();\n      router.push('/admin/users');\n    } catch (error) {\n      toast({\n        variant: 'destructive',\n        description: (error as Error).message,\n      });\n    }\n  };\n\n  return (\n    <Form {...form}>\n      <form method='POST' onSubmit={form.handleSubmit(onSubmit)}>\n        {/* Email */}\n        <div>\n          <FormField\n            control={form.control}\n            name='email'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof updateUserSchema>,\n                'email'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Email</FormLabel>\n                <FormControl>\n                  <Input\n                    disabled={true}\n                    placeholder='Enter user email'\n                    {...field}\n                  />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        {/* Name */}\n        <div>\n          <FormField\n            control={form.control}\n            name='name'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof updateUserSchema>,\n                'name'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Name</FormLabel>\n                <FormControl>\n                  <Input placeholder='Enter user name' {...field} />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        {/* Role */}\n        <div>\n          <FormField\n            control={form.control}\n            name='role'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof updateUserSchema>,\n                'role'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Role</FormLabel>\n                <Select\n                  onValueChange={field.onChange}\n                  value={field.value.toString()}\n                >\n                  <FormControl>\n                    <SelectTrigger>\n                      <SelectValue placeholder='Select a role' />\n                    </SelectTrigger>\n                  </FormControl>\n                  <SelectContent>\n                    {USER_ROLES.map((role) => (\n                      <SelectItem key={role} value={role}>\n                        {role.charAt(0).toUpperCase() + role.slice(1)}\n                      </SelectItem>\n                    ))}\n                  </SelectContent>\n                </Select>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <div className='flex-between mt-6'>\n          <Button\n            type='submit'\n            className='w-full'\n            disabled={form.formState.isSubmitting}\n          >\n            {form.formState.isSubmitting ? 'Submitting...' : 'Update User'}\n          </Button>\n        </div>\n      </form>\n    </Form>\n  );\n};\n\nexport default UpdateUserForm;\n"
  },
  {
    "path": "app/admin/users/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { getAllUsers, deleteUser } from '@/lib/actions/user.actions';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport { formatId } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport Link from 'next/link';\nimport Pagination from '@/components/shared/pagination';\nimport { Badge } from '@/components/ui/badge';\nimport DeleteDialog from '@/components/shared/delete-dialog';\nimport { requireAdmin } from '@/lib/auth-guard';\n\nexport const metadata: Metadata = {\n  title: 'Admin Users',\n};\n\nconst AdminUserPage = async (props: {\n  searchParams: Promise<{\n    page: string;\n    query: string;\n  }>;\n}) => {\n  await requireAdmin();\n\n  const { page = '1', query: searchText } = await props.searchParams;\n\n  const users = await getAllUsers({ page: Number(page), query: searchText });\n\n  return (\n    <div className='space-y-2'>\n      <div className='flex items-center gap-3'>\n        <h1 className='h2-bold'>Users</h1>\n        {searchText && (\n          <div>\n            Filtered by <i>&quot;{searchText}&quot;</i>{' '}\n            <Link href='/admin/users'>\n              <Button variant='outline' size='sm'>\n                Remove Filter\n              </Button>\n            </Link>\n          </div>\n        )}\n      </div>\n      <div className='overflow-x-auto'>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>ID</TableHead>\n              <TableHead>NAME</TableHead>\n              <TableHead>EMAIL</TableHead>\n              <TableHead>ROLE</TableHead>\n              <TableHead>ACTIONS</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {users.data.map((user) => (\n              <TableRow key={user.id}>\n                <TableCell>{formatId(user.id)}</TableCell>\n                <TableCell>{user.name}</TableCell>\n                <TableCell>{user.email}</TableCell>\n                <TableCell>\n                  {user.role === 'user' ? (\n                    <Badge variant='secondary'>User</Badge>\n                  ) : (\n                    <Badge variant='default'>Admin</Badge>\n                  )}\n                </TableCell>\n                <TableCell>\n                  <Button asChild variant='outline' size='sm'>\n                    <Link href={`/admin/users/${user.id}`}>Edit</Link>\n                  </Button>\n                  <DeleteDialog id={user.id} action={deleteUser} />\n                </TableCell>\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {users.totalPages > 1 && (\n          <Pagination page={Number(page) || 1} totalPages={users?.totalPages} />\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default AdminUserPage;\n"
  },
  {
    "path": "app/api/auth/[...nextauth]/route.ts",
    "content": "import { handlers } from '@/auth';\nexport const { GET, POST } = handlers;\n"
  },
  {
    "path": "app/api/uploadthing/core.ts",
    "content": "import { createUploadthing, type FileRouter } from 'uploadthing/next';\nimport { UploadThingError } from 'uploadthing/server';\nimport { auth } from '@/auth';\n\nconst f = createUploadthing();\n\nexport const ourFileRouter = {\n  imageUploader: f({\n    image: { maxFileSize: '4MB' },\n  })\n    .middleware(async () => {\n      const session = await auth();\n      if (!session) throw new UploadThingError('Unauthorized');\n      return { userId: session?.user?.id };\n    })\n    .onUploadComplete(async ({ metadata }) => {\n      return { uploadedBy: metadata.userId };\n    }),\n} satisfies FileRouter;\nexport type OurFileRouter = typeof ourFileRouter;\n"
  },
  {
    "path": "app/api/uploadthing/route.ts",
    "content": "import { createRouteHandler } from 'uploadthing/next';\nimport { ourFileRouter } from './core';\n\nexport const { GET, POST } = createRouteHandler({\n  router: ourFileRouter,\n});\n"
  },
  {
    "path": "app/api/webhooks/stripe/route.ts",
    "content": "import { NextRequest, NextResponse } from 'next/server';\nimport Stripe from 'stripe';\nimport { updateOrderToPaid } from '@/lib/actions/order.actions';\n\nexport async function POST(req: NextRequest) {\n  // Build the webhook event\n  const event = await Stripe.webhooks.constructEvent(\n    await req.text(),\n    req.headers.get('stripe-signature') as string,\n    process.env.STRIPE_WEBHOOK_SECRET as string\n  );\n\n  // Check for successful payment\n  if (event.type === 'charge.succeeded') {\n    const { object } = event.data;\n\n    // Update order status\n    await updateOrderToPaid({\n      orderId: object.metadata.orderId,\n      paymentResult: {\n        id: object.id,\n        status: 'COMPLETED',\n        email_address: object.billing_details.email!,\n        pricePaid: (object.amount / 100).toFixed(),\n      },\n    });\n\n    return NextResponse.json({\n      message: 'updateOrderToPaid was successful',\n    });\n  }\n\n  return NextResponse.json({\n    message: 'event is not charge.succeeded',\n  });\n}\n"
  },
  {
    "path": "app/layout.tsx",
    "content": "import type { Metadata } from 'next';\nimport { Inter } from 'next/font/google';\nimport '@/assets/styles/globals.css';\nimport { APP_DESCRIPTION, APP_NAME, SERVER_URL } from '@/lib/constants';\nimport { ThemeProvider } from 'next-themes';\nimport { Toaster } from '@/components/ui/toaster';\n\nconst inter = Inter({ subsets: ['latin'] });\n\nexport const metadata: Metadata = {\n  title: {\n    template: `%s | Prostore`,\n    default: APP_NAME,\n  },\n  description: APP_DESCRIPTION,\n  metadataBase: new URL(SERVER_URL),\n};\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang='en' suppressHydrationWarning>\n      <body className={`${inter.className} antialiased`}>\n        <ThemeProvider\n          attribute='class'\n          defaultTheme='light'\n          enableSystem\n          disableTransitionOnChange\n        >\n          {children}\n          <Toaster />\n        </ThemeProvider>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "app/loading.tsx",
    "content": "import Image from 'next/image';\nimport loader from '@/assets/loader.gif';\n\nconst LoadingPage = () => {\n  return (\n    <div\n      style={{\n        display: 'flex',\n        justifyContent: 'center',\n        alignItems: 'center',\n        height: '100vh',\n        width: '100vw',\n      }}\n    >\n      <Image src={loader} height={150} width={150} alt='Loading...' />\n    </div>\n  );\n};\n\nexport default LoadingPage;\n"
  },
  {
    "path": "app/not-found.tsx",
    "content": "'use client';\nimport { APP_NAME } from '@/lib/constants';\nimport Image from 'next/image';\nimport { Button } from '@/components/ui/button';\nimport Link from 'next/link';\n\nconst NotFoundPage = () => {\n  return (\n    <div className='flex flex-col items-center justify-center min-h-screen'>\n      <Image\n        src='/images/logo.svg'\n        width={48}\n        height={48}\n        alt={`${APP_NAME} logo`}\n        priority={true}\n      />\n      <div className='p-6 w-1/3 rounded-lg shadow-md text-center'>\n        <h1 className='text-3xl font-bold mb-4'>Not Found</h1>\n        <p className='text-destructive'>Could not find requested page</p>\n        <Button variant='outline' className='mt-4 ml-2' asChild>\n          <Link href='/'>Back To Home</Link>\n        </Button>\n      </div>\n    </div>\n  );\n};\n\nexport default NotFoundPage;\n"
  },
  {
    "path": "app/unauthorized/page.tsx",
    "content": "import { Button } from '@/components/ui/button'\nimport { Metadata } from 'next'\nimport Link from 'next/link'\n\nexport const metadata: Metadata = {\n  title: 'Unauthorized Access',\n}\n\nexport default function UnauthorizedPage() {\n  return (\n    <div className='container mx-auto flex h-[calc(100vh-200px)] flex-col items-center justify-center space-y-4'>\n      <h1 className='h1-bold text-4xl'>Unauthorized Access</h1>\n      <p className='text-muted-foreground'>\n        You do not have permission to access this page.\n      </p>\n      <Button asChild>\n        <Link href='/'>Return Home</Link>\n      </Button>\n    </div>\n  )\n}\n"
  },
  {
    "path": "app/user/layout.tsx",
    "content": "import { APP_NAME } from '@/lib/constants';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport Menu from '@/components/shared/header/menu';\nimport MainNav from './main-nav';\n\nexport default function UserLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <>\n      <div className='flex flex-col'>\n        <div className='border-b container mx-auto'>\n          <div className='flex items-center h-16 px-4'>\n            <Link href='/' className='w-22'>\n              <Image\n                src='/images/logo.svg'\n                height={48}\n                width={48}\n                alt={APP_NAME}\n              />\n            </Link>\n            <MainNav className='mx-6' />\n            <div className='ml-auto items-center flex space-x-4'>\n              <Menu />\n            </div>\n          </div>\n        </div>\n\n        <div className='flex-1 space-y-4 p-8 pt-6 container mx-auto'>\n          {children}\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "app/user/main-nav.tsx",
    "content": "'use client';\nimport Link from 'next/link';\nimport { usePathname } from 'next/navigation';\nimport { cn } from '@/lib/utils';\nimport React from 'react';\n\nconst links = [\n  {\n    title: 'Profile',\n    href: '/user/profile',\n  },\n  {\n    title: 'Orders',\n    href: '/user/orders',\n  },\n];\n\nconst MainNav = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLElement>) => {\n  const pathname = usePathname();\n  return (\n    <nav\n      className={cn('flex items-center space-x-4 lg:space-x-6', className)}\n      {...props}\n    >\n      {links.map((item) => (\n        <Link\n          key={item.href}\n          href={item.href}\n          className={cn(\n            'text-sm font-medium transition-colors hover:text-primary',\n            pathname.includes(item.href) ? '' : 'text-muted-foreground'\n          )}\n        >\n          {item.title}\n        </Link>\n      ))}\n    </nav>\n  );\n};\n\nexport default MainNav;\n"
  },
  {
    "path": "app/user/orders/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { getMyOrders } from '@/lib/actions/order.actions';\nimport { formatCurrency, formatDateTime, formatId } from '@/lib/utils';\nimport Link from 'next/link';\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from '@/components/ui/table';\nimport Pagination from '@/components/shared/pagination';\n\nexport const metadata: Metadata = {\n  title: 'My Orders',\n};\n\nconst OrdersPage = async (props: {\n  searchParams: Promise<{ page: string }>;\n}) => {\n  const { page } = await props.searchParams;\n\n  const orders = await getMyOrders({\n    page: Number(page) || 1,\n  });\n\n  return (\n    <div className='space-y-2'>\n      <h2 className='h2-bold'>Orders</h2>\n      <div className='overflow-x-auto'>\n        <Table>\n          <TableHeader>\n            <TableRow>\n              <TableHead>ID</TableHead>\n              <TableHead>DATE</TableHead>\n              <TableHead>TOTAL</TableHead>\n              <TableHead>PAID</TableHead>\n              <TableHead>DELIVERED</TableHead>\n              <TableHead>ACTIONS</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {orders.data.map((order) => (\n              <TableRow key={order.id}>\n                <TableCell>{formatId(order.id)}</TableCell>\n                <TableCell>\n                  {formatDateTime(order.createdAt).dateTime}\n                </TableCell>\n                <TableCell>{formatCurrency(order.totalPrice)}</TableCell>\n                <TableCell>\n                  {order.isPaid && order.paidAt\n                    ? formatDateTime(order.paidAt).dateTime\n                    : 'Not Paid'}\n                </TableCell>\n                <TableCell>\n                  {order.isDelivered && order.deliveredAt\n                    ? formatDateTime(order.deliveredAt).dateTime\n                    : 'Not Delivered'}\n                </TableCell>\n                <TableCell>\n                  <Link href={`/order/${order.id}`}>\n                    <span className='px-2'>Details</span>\n                  </Link>\n                </TableCell>\n              </TableRow>\n            ))}\n          </TableBody>\n        </Table>\n        {orders.totalPages > 1 && (\n          <Pagination\n            page={Number(page) || 1}\n            totalPages={orders?.totalPages}\n          />\n        )}\n      </div>\n    </div>\n  );\n};\n\nexport default OrdersPage;\n"
  },
  {
    "path": "app/user/profile/page.tsx",
    "content": "import { Metadata } from 'next';\nimport { auth } from '@/auth';\nimport { SessionProvider } from 'next-auth/react';\nimport ProfileForm from './profile-form';\n\nexport const metadata: Metadata = {\n  title: 'Customer Profile',\n};\n\nconst Profile = async () => {\n  const session = await auth();\n\n  return (\n    <SessionProvider session={session}>\n      <div className='max-w-md mx-auto space-y-4'>\n        <h2 className='h2-bold'>Profile</h2>\n        <ProfileForm />\n      </div>\n    </SessionProvider>\n  );\n};\n\nexport default Profile;\n"
  },
  {
    "path": "app/user/profile/profile-form.tsx",
    "content": "'use client';\nimport { Button } from '@/components/ui/button';\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormMessage,\n} from '@/components/ui/form';\nimport { Input } from '@/components/ui/input';\nimport { useToast } from '@/hooks/use-toast';\nimport { updateProfile } from '@/lib/actions/user.actions';\nimport { updateProfileSchema } from '@/lib/validators';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useSession } from 'next-auth/react';\nimport { useForm } from 'react-hook-form';\nimport { z } from 'zod';\n\nconst ProfileForm = () => {\n  const { data: session, update } = useSession();\n\n  const form = useForm<z.infer<typeof updateProfileSchema>>({\n    resolver: zodResolver(updateProfileSchema),\n    defaultValues: {\n      name: session?.user?.name ?? '',\n      email: session?.user?.email ?? '',\n    },\n  });\n\n  const { toast } = useToast();\n\n  const onSubmit = async (values: z.infer<typeof updateProfileSchema>) => {\n    const res = await updateProfile(values);\n\n    if (!res.success) {\n      return toast({\n        variant: 'destructive',\n        description: res.message,\n      });\n    }\n\n    const newSession = {\n      ...session,\n      user: {\n        ...session?.user,\n        name: values.name,\n      },\n    };\n\n    await update(newSession);\n\n    toast({\n      description: res.message,\n    });\n  };\n\n  return (\n    <Form {...form}>\n      <form\n        className='flex flex-col gap-5'\n        onSubmit={form.handleSubmit(onSubmit)}\n      >\n        <div className='flex flex-col gap-5'>\n          <FormField\n            control={form.control}\n            name='email'\n            render={({ field }) => (\n              <FormItem className='w-full'>\n                <FormControl>\n                  <Input\n                    disabled\n                    placeholder='Email'\n                    className='input-field'\n                    {...field}\n                  />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n          <FormField\n            control={form.control}\n            name='name'\n            render={({ field }) => (\n              <FormItem className='w-full'>\n                <FormControl>\n                  <Input\n                    placeholder='Name'\n                    className='input-field'\n                    {...field}\n                  />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <Button\n          type='submit'\n          size='lg'\n          className='button col-span-2 w-full'\n          disabled={form.formState.isSubmitting}\n        >\n          {form.formState.isSubmitting ? 'Submitting...' : 'Update Profile'}\n        </Button>\n      </form>\n    </Form>\n  );\n};\n\nexport default ProfileForm;\n"
  },
  {
    "path": "assets/styles/globals.css",
    "content": "@tailwind base;\r\n@tailwind components;\r\n@tailwind utilities;\r\n\r\n@layer utilities {\r\n  .wrapper {\r\n    @apply max-w-7xl lg:mx-auto p-5 md:px-10 w-full;\r\n  }\r\n\r\n  .flex-start {\r\n    @apply flex justify-start items-center;\r\n  }\r\n  .flex-center {\r\n    @apply flex justify-center items-center;\r\n  }\r\n\r\n  .flex-between {\r\n    @apply flex justify-between items-center;\r\n  }\r\n\r\n  .h1-bold {\r\n    @apply font-bold text-3xl lg:text-4xl;\r\n  }\r\n\r\n  .h2-bold {\r\n    @apply font-bold text-2xl lg:text-3xl;\r\n  }\r\n\r\n  .h3-bold {\r\n    @apply font-bold text-xl lg:text-2xl;\r\n  }\r\n}\r\n\r\n@layer base {\r\n  :root {\r\n    --background: 0 0% 100%;\r\n    --foreground: 222.2 84% 4.9%;\r\n    --card: 0 0% 100%;\r\n    --card-foreground: 222.2 84% 4.9%;\r\n    --popover: 0 0% 100%;\r\n    --popover-foreground: 222.2 84% 4.9%;\r\n    --primary: 222.2 47.4% 11.2%;\r\n    --primary-foreground: 210 40% 98%;\r\n    --secondary: 210 40% 96.1%;\r\n    --secondary-foreground: 222.2 47.4% 11.2%;\r\n    --muted: 210 40% 96.1%;\r\n    --muted-foreground: 215.4 16.3% 46.9%;\r\n    --accent: 210 40% 96.1%;\r\n    --accent-foreground: 222.2 47.4% 11.2%;\r\n    --destructive: 0 84.2% 60.2%;\r\n    --destructive-foreground: 210 40% 98%;\r\n    --border: 214.3 31.8% 91.4%;\r\n    --input: 214.3 31.8% 91.4%;\r\n    --ring: 222.2 84% 4.9%;\r\n    --chart-1: 12 76% 61%;\r\n    --chart-2: 173 58% 39%;\r\n    --chart-3: 197 37% 24%;\r\n    --chart-4: 43 74% 66%;\r\n    --chart-5: 27 87% 67%;\r\n    --radius: 0.5rem;\r\n  }\r\n  .dark {\r\n    --background: 222.2 84% 4.9%;\r\n    --foreground: 210 40% 98%;\r\n    --card: 222.2 84% 4.9%;\r\n    --card-foreground: 210 40% 98%;\r\n    --popover: 222.2 84% 4.9%;\r\n    --popover-foreground: 210 40% 98%;\r\n    --primary: 210 40% 98%;\r\n    --primary-foreground: 222.2 47.4% 11.2%;\r\n    --secondary: 217.2 32.6% 17.5%;\r\n    --secondary-foreground: 210 40% 98%;\r\n    --muted: 217.2 32.6% 17.5%;\r\n    --muted-foreground: 215 20.2% 65.1%;\r\n    --accent: 217.2 32.6% 17.5%;\r\n    --accent-foreground: 210 40% 98%;\r\n    --destructive: 0 62.8% 30.6%;\r\n    --destructive-foreground: 210 40% 98%;\r\n    --border: 217.2 32.6% 17.5%;\r\n    --input: 217.2 32.6% 17.5%;\r\n    --ring: 212.7 26.8% 83.9%;\r\n    --chart-1: 220 70% 50%;\r\n    --chart-2: 160 60% 45%;\r\n    --chart-3: 30 80% 55%;\r\n    --chart-4: 280 65% 60%;\r\n    --chart-5: 340 75% 55%;\r\n  }\r\n}\r\n\r\n@layer base {\r\n  body {\r\n    @apply bg-background text-foreground;\r\n  }\r\n}\r\n\r\n@layer base {\r\n  * {\r\n    @apply border-border;\r\n  }\r\n  body {\r\n    @apply bg-background text-foreground;\r\n  }\r\n}\r\n\r\n/* Uploadthing button text override */\r\nhtml.dark .upload-field .text-white {\r\n  color: #ffffff !important;\r\n}\r\n\r\n.upload-field .text-white {\r\n  color: #000 !important;\r\n}\r\n"
  },
  {
    "path": "auth.config.ts",
    "content": "import type { NextAuthConfig } from 'next-auth';\nimport { NextResponse } from 'next/server';\n\nexport const authConfig = {\n  providers: [], // Required by NextAuthConfig type\n  callbacks: {\n    authorized({ request, auth }) {\n      // Array of regex patterns of paths we want to protect\n      const protectedPaths = [\n        /\\/shipping-address/,\n        /\\/payment-method/,\n        /\\/place-order/,\n        /\\/profile/,\n        /\\/user\\/(.*)/,\n        /\\/order\\/(.*)/,\n        /\\/admin/,\n      ];\n\n      // Get pathname from the req URL object\n      const { pathname } = request.nextUrl;\n      // Check if user is not authenticated and accessing a protected path\n      if (!auth && protectedPaths.some((p) => p.test(pathname))) return false;\n\n      // Check for session cart cookie\n      if (!request.cookies.get('sessionCartId')) {\n        // Generate new session cart id cookie\n        const sessionCartId = crypto.randomUUID();\n\n        // Create new response and add the new headers\n        const response = NextResponse.next({\n          request: {\n            headers: new Headers(request.headers),\n          },\n        });\n\n        // Set newly generated sessionCartId in the response cookies\n        response.cookies.set('sessionCartId', sessionCartId);\n\n        return response;\n      }\n\n      return true;\n    },\n  },\n} satisfies NextAuthConfig;\n"
  },
  {
    "path": "auth.ts",
    "content": "import NextAuth from 'next-auth';\nimport { authConfig } from './auth.config';\nimport { PrismaAdapter } from '@auth/prisma-adapter';\nimport { prisma } from '@/db/prisma';\nimport { cookies } from 'next/headers';\nimport { compare } from './lib/encrypt';\nimport CredentialsProvider from 'next-auth/providers/credentials';\n\nexport const { handlers, auth, signIn, signOut } = NextAuth({\n  pages: {\n    signIn: '/sign-in',\n    error: '/sign-in',\n  },\n  session: {\n    strategy: 'jwt' as const,\n    maxAge: 30 * 24 * 60 * 60, // 30 days\n  },\n  adapter: PrismaAdapter(prisma),\n  providers: [\n    CredentialsProvider({\n      credentials: {\n        email: { type: 'email' },\n        password: { type: 'password' },\n      },\n      async authorize(credentials) {\n        if (credentials == null) return null;\n\n        // Find user in database\n        const user = await prisma.user.findFirst({\n          where: {\n            email: credentials.email as string,\n          },\n        });\n\n        // Check if user exists and if the password matches\n        if (user && user.password) {\n          const isMatch = await compare(\n            credentials.password as string,\n            user.password\n          );\n\n          // If password is correct, return user\n          if (isMatch) {\n            return {\n              id: user.id,\n              name: user.name,\n              email: user.email,\n              role: user.role,\n            };\n          }\n        }\n        // If user does not exist or password does not match return null\n        return null;\n      },\n    }),\n  ],\n  callbacks: {\n    ...authConfig.callbacks,\n    async session({ session, user, trigger, token }) {\n      // Set the user ID from the token\n      session.user.id = token.sub;\n      session.user.role = token.role;\n      session.user.name = token.name;\n\n      // If there is an update, set the user name\n      if (trigger === 'update') {\n        session.user.name = user.name;\n      }\n\n      return session;\n    },\n    async jwt({ token, user, trigger, session }) {\n      // Assign user fields to token\n      if (user) {\n        token.id = user.id;\n        token.role = user.role;\n\n        // If user has no name then use the email\n        if (user.name === 'NO_NAME') {\n          token.name = user.email!.split('@')[0];\n\n          // Update database to reflect the token name\n          await prisma.user.update({\n            where: { id: user.id },\n            data: { name: token.name },\n          });\n        }\n\n        if (trigger === 'signIn' || trigger === 'signUp') {\n          const cookiesObject = await cookies();\n          const sessionCartId = cookiesObject.get('sessionCartId')?.value;\n\n          if (sessionCartId) {\n            const sessionCart = await prisma.cart.findFirst({\n              where: { sessionCartId },\n            });\n\n            if (sessionCart) {\n              // Delete current user cart\n              await prisma.cart.deleteMany({\n                where: { userId: user.id },\n              });\n\n              // Assign new cart\n              await prisma.cart.update({\n                where: { id: sessionCart.id },\n                data: { userId: user.id },\n              });\n            }\n          }\n        }\n      }\n\n      // Handle session updates\n      if (session?.user.name && trigger === 'update') {\n        token.name = session.user.name;\n      }\n\n      return token;\n    },\n  },\n});\n"
  },
  {
    "path": "components/admin/admin-search.tsx",
    "content": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport { Input } from '../ui/input';\n\nconst AdminSearch = () => {\n  const pathname = usePathname();\n  const formActionUrl = pathname.includes('/admin/orders')\n    ? '/admin/orders'\n    : pathname.includes('/admin/users')\n      ? '/admin/users'\n      : '/admin/products';\n\n  const searchParams = useSearchParams();\n  const [queryValue, setQueryValue] = useState(searchParams.get('query') || '');\n\n  useEffect(() => {\n    setQueryValue(searchParams.get('query') || '');\n  }, [searchParams]);\n\n  return (\n    <form action={formActionUrl} method='GET'>\n      <Input\n        type='search'\n        placeholder='Search...'\n        name='query'\n        value={queryValue}\n        onChange={(e) => setQueryValue(e.target.value)}\n        className='md:w-[100px] lg:w-[300px]'\n      />\n      <button className='sr-only' type='submit'>\n        Search\n      </button>\n    </form>\n  );\n};\n\nexport default AdminSearch;\n"
  },
  {
    "path": "components/admin/product-form.tsx",
    "content": "'use client';\n\nimport { useToast } from '@/hooks/use-toast';\nimport { productDefaultValues } from '@/lib/constants';\nimport { insertProductSchema, updateProductSchema } from '@/lib/validators';\nimport { Product } from '@/types';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useRouter } from 'next/navigation';\nimport { ControllerRenderProps, SubmitHandler, useForm } from 'react-hook-form';\nimport { z } from 'zod';\nimport {\n  Form,\n  FormControl,\n  FormField,\n  FormItem,\n  FormLabel,\n  FormMessage,\n} from '../ui/form';\nimport slugify from 'slugify';\nimport { Input } from '../ui/input';\nimport { Button } from '../ui/button';\nimport { Textarea } from '../ui/textarea';\nimport { createProduct, updateProduct } from '@/lib/actions/product.actions';\nimport { UploadButton } from '@/lib/uploadthing';\nimport { Card, CardContent } from '../ui/card';\nimport Image from 'next/image';\nimport { Checkbox } from '../ui/checkbox';\n\nconst ProductForm = ({\n  type,\n  product,\n  productId,\n}: {\n  type: 'Create' | 'Update';\n  product?: Product;\n  productId?: string;\n}) => {\n  const router = useRouter();\n  const { toast } = useToast();\n\n  const form = useForm<z.infer<typeof insertProductSchema>>({\n    resolver:\n      type === 'Update'\n        ? zodResolver(updateProductSchema)\n        : zodResolver(insertProductSchema),\n    defaultValues:\n      product && type === 'Update' ? product : productDefaultValues,\n  });\n\n  const onSubmit: SubmitHandler<z.infer<typeof insertProductSchema>> = async (\n    values\n  ) => {\n    // On Create\n    if (type === 'Create') {\n      const res = await createProduct(values);\n\n      if (!res.success) {\n        toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n      } else {\n        toast({\n          description: res.message,\n        });\n        router.push('/admin/products');\n      }\n    }\n\n    // On Update\n    if (type === 'Update') {\n      if (!productId) {\n        router.push('/admin/products');\n        return;\n      }\n\n      const res = await updateProduct({ ...values, id: productId });\n\n      if (!res.success) {\n        toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n      } else {\n        toast({\n          description: res.message,\n        });\n        router.push('/admin/products');\n      }\n    }\n  };\n\n  const images = form.watch('images');\n  const isFeatured = form.watch('isFeatured');\n  const banner = form.watch('banner');\n\n  return (\n    <Form {...form}>\n      <form\n        method='POST'\n        onSubmit={form.handleSubmit(onSubmit)}\n        className='space-y-8'\n      >\n        <div className='flex flex-col md:flex-row gap-5'>\n          {/* Name */}\n          <FormField\n            control={form.control}\n            name='name'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'name'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Name</FormLabel>\n                <FormControl>\n                  <Input placeholder='Enter product name' {...field} />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n          {/* Slug */}\n          <FormField\n            control={form.control}\n            name='slug'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'slug'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Name</FormLabel>\n                <FormControl>\n                  <div className='relative'>\n                    <Input placeholder='Enter slug' {...field} />\n                    <Button\n                      type='button'\n                      className='bg-gray-500 hover:bg-gray-600 text-white px-4 py-1 mt-2'\n                      onClick={() => {\n                        form.setValue(\n                          'slug',\n                          slugify(form.getValues('name'), { lower: true })\n                        );\n                      }}\n                    >\n                      Generate\n                    </Button>\n                  </div>\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <div className='flex flex-col md:flex-row gap-5'>\n          {/* Category */}\n          <FormField\n            control={form.control}\n            name='category'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'category'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Category</FormLabel>\n                <FormControl>\n                  <Input placeholder='Enter category' {...field} />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n          {/* Brand */}\n          <FormField\n            control={form.control}\n            name='brand'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'brand'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Brand</FormLabel>\n                <FormControl>\n                  <Input placeholder='Enter brand' {...field} />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <div className='flex flex-col md:flex-row gap-5'>\n          {/* Price */}\n          <FormField\n            control={form.control}\n            name='price'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'price'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Price</FormLabel>\n                <FormControl>\n                  <Input placeholder='Enter product price' {...field} />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n          {/* Stock */}\n          <FormField\n            control={form.control}\n            name='stock'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'stock'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Stock</FormLabel>\n                <FormControl>\n                  <Input placeholder='Enter stock' {...field} />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <div className='upload-field flex flex-col md:flex-row gap-5'>\n          {/* Images */}\n          <FormField\n            control={form.control}\n            name='images'\n            render={() => (\n              <FormItem className='w-full'>\n                <FormLabel>Images</FormLabel>\n                <Card>\n                  <CardContent className='space-y-2 mt-2 min-h-48'>\n                    <div className='flex-start space-x-2'>\n                      {images.map((image: string) => (\n                        <Image\n                          key={image}\n                          src={image}\n                          alt='product image'\n                          className='w-20 h-20 object-cover object-center rounded-sm'\n                          width={100}\n                          height={100}\n                        />\n                      ))}\n                      <FormControl>\n                        <UploadButton\n                          endpoint='imageUploader'\n                          onClientUploadComplete={(res: { url: string }[]) => {\n                            form.setValue('images', [...images, res[0].url]);\n                          }}\n                          onUploadError={(error: Error) => {\n                            toast({\n                              variant: 'destructive',\n                              description: `ERROR! ${error.message}`,\n                            });\n                          }}\n                        />\n                      </FormControl>\n                    </div>\n                  </CardContent>\n                </Card>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <div className='upload-field'>\n          {/* isFeatured */}\n          Featured Product\n          <Card>\n            <CardContent className='space-y-2 mt-2'>\n              <FormField\n                control={form.control}\n                name='isFeatured'\n                render={({ field }) => (\n                  <FormItem className='space-x-2 items-center'>\n                    <FormControl>\n                      <Checkbox\n                        checked={field.value}\n                        onCheckedChange={field.onChange}\n                      />\n                    </FormControl>\n                    <FormLabel>Is Featured?</FormLabel>\n                  </FormItem>\n                )}\n              />\n              {isFeatured && banner && (\n                <Image\n                  src={banner}\n                  alt='banner image'\n                  className='w-full object-cover object-center rounded-sm'\n                  width={1920}\n                  height={680}\n                />\n              )}\n\n              {isFeatured && !banner && (\n                <UploadButton\n                  endpoint='imageUploader'\n                  onClientUploadComplete={(res: { url: string }[]) => {\n                    form.setValue('banner', res[0].url);\n                  }}\n                  onUploadError={(error: Error) => {\n                    toast({\n                      variant: 'destructive',\n                      description: `ERROR! ${error.message}`,\n                    });\n                  }}\n                />\n              )}\n            </CardContent>\n          </Card>\n        </div>\n        <div>\n          {/* Description */}\n          <FormField\n            control={form.control}\n            name='description'\n            render={({\n              field,\n            }: {\n              field: ControllerRenderProps<\n                z.infer<typeof insertProductSchema>,\n                'description'\n              >;\n            }) => (\n              <FormItem className='w-full'>\n                <FormLabel>Description</FormLabel>\n                <FormControl>\n                  <Textarea\n                    placeholder='Enter product description'\n                    className='resize-none'\n                    {...field}\n                  />\n                </FormControl>\n                <FormMessage />\n              </FormItem>\n            )}\n          />\n        </div>\n        <div>\n          <Button\n            type='submit'\n            size='lg'\n            disabled={form.formState.isSubmitting}\n            className='button col-span-2 w-full'\n          >\n            {form.formState.isSubmitting ? 'Submitting' : `${type} Product`}\n          </Button>\n        </div>\n      </form>\n    </Form>\n  );\n};\n\nexport default ProductForm;\n"
  },
  {
    "path": "components/deal-countdown.tsx",
    "content": "'use client';\n\nimport Link from 'next/link';\nimport { Button } from './ui/button';\nimport Image from 'next/image';\nimport { useEffect, useState } from 'react';\n\n// Static target date (replace with desired date)\nconst TARGET_DATE = new Date('2025-01-20T00:00:00');\n\n// Function to calculate the time remaining\nconst calculateTimeRemaining = (targetDate: Date) => {\n  const currentTime = new Date();\n  const timeDifference = Math.max(Number(targetDate) - Number(currentTime), 0);\n  return {\n    days: Math.floor(timeDifference / (1000 * 60 * 60 * 24)),\n    hours: Math.floor(\n      (timeDifference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)\n    ),\n    minutes: Math.floor((timeDifference % (1000 * 60 * 60)) / (1000 * 60)),\n    seconds: Math.floor((timeDifference % (1000 * 60)) / 1000),\n  };\n};\n\nconst DealCountdown = () => {\n  const [time, setTime] = useState<ReturnType<typeof calculateTimeRemaining>>();\n\n  useEffect(() => {\n    // Calculate initial time on client\n    setTime(calculateTimeRemaining(TARGET_DATE));\n\n    const timerInterval = setInterval(() => {\n      const newTime = calculateTimeRemaining(TARGET_DATE);\n      setTime(newTime);\n\n      if (\n        newTime.days === 0 &&\n        newTime.hours === 0 &&\n        newTime.minutes === 0 &&\n        newTime.seconds === 0\n      ) {\n        clearInterval(timerInterval);\n      }\n\n      return () => clearInterval(timerInterval);\n    }, 1000);\n  }, []);\n\n  if (!time) {\n    return (\n      <section className='grid grid-cols-1 md:grid-cols-2 my-20'>\n        <div className='flex flex-col gap-2 justify-center'>\n          <h3 className='text-3xl font-bold'>Loading Countdown...</h3>\n        </div>\n      </section>\n    );\n  }\n\n  if (\n    time.days === 0 &&\n    time.hours === 0 &&\n    time.minutes === 0 &&\n    time.seconds === 0\n  ) {\n    return (\n      <section className='grid grid-cols-1 md:grid-cols-2 my-20'>\n        <div className='flex flex-col gap-2 justify-center'>\n          <h3 className='text-3xl font-bold'>Deal Has Ended</h3>\n          <p>\n            This deal is no longer available. Check out our latest promotions!\n          </p>\n\n          <div className='text-center'>\n            <Button asChild>\n              <Link href='/search'>View Products</Link>\n            </Button>\n          </div>\n        </div>\n        <div className='flex justify-center'>\n          <Image\n            src='/images/promo.jpg'\n            alt='promotion'\n            width={300}\n            height={200}\n          />\n        </div>\n      </section>\n    );\n  }\n\n  return (\n    <section className='grid grid-cols-1 md:grid-cols-2 my-20'>\n      <div className='flex flex-col gap-2 justify-center'>\n        <h3 className='text-3xl font-bold'>Deal Of The Month</h3>\n        <p>\n          Get ready for a shopping experience like never before with our Deals\n          of the Month! Every purchase comes with exclusive perks and offers,\n          making this month a celebration of savvy choices and amazing deals.\n          Don&apos;t miss out! 🎁🛒\n        </p>\n        <ul className='grid grid-cols-4'>\n          <StatBox label='Days' value={time.days} />\n          <StatBox label='Hours' value={time.hours} />\n          <StatBox label='Minutes' value={time.minutes} />\n          <StatBox label='Seconds' value={time.seconds} />\n        </ul>\n        <div className='text-center'>\n          <Button asChild>\n            <Link href='/search'>View Products</Link>\n          </Button>\n        </div>\n      </div>\n      <div className='flex justify-center'>\n        <Image\n          src='/images/promo.jpg'\n          alt='promotion'\n          width={300}\n          height={200}\n        />\n      </div>\n    </section>\n  );\n};\n\nconst StatBox = ({ label, value }: { label: string; value: number }) => (\n  <li className='p-4 w-full text-center'>\n    <p className='text-3xl font-bold'>{value}</p>\n    <p>{label}</p>\n  </li>\n);\n\nexport default DealCountdown;\n"
  },
  {
    "path": "components/footer.tsx",
    "content": "import { APP_NAME } from '@/lib/constants';\n\nconst Footer = () => {\n  const currentYear = new Date().getFullYear();\n\n  return (\n    <footer className='border-t'>\n      <div className='p-5 flex-center'>\n        {currentYear} {APP_NAME}. All Rights Reserved\n      </div>\n    </footer>\n  );\n};\n\nexport default Footer;\n"
  },
  {
    "path": "components/icon-boxes.tsx",
    "content": "import { DollarSign, Headset, ShoppingBag, WalletCards } from 'lucide-react';\nimport { Card, CardContent } from './ui/card';\n\nconst IconBoxes = () => {\n  return (\n    <div>\n      <Card>\n        <CardContent className='grid md:grid-cols-4 gap-4 p-4'>\n          <div className='space-y-2'>\n            <ShoppingBag />\n            <div className='text-sm font-bold'>Free Shipping</div>\n            <div className='text-sm text-muted-foreground'>\n              Free shipping on orders above $100\n            </div>\n          </div>\n          <div className='space-y-2'>\n            <DollarSign />\n            <div className='text-sm font-bold'>Money Back Guarantee</div>\n            <div className='text-sm text-muted-foreground'>\n              Within 30 days of purchase\n            </div>\n          </div>\n          <div className='space-y-2'>\n            <WalletCards />\n            <div className='text-sm font-bold'>Flexible Payment</div>\n            <div className='text-sm text-muted-foreground'>\n              Pay with credit card, PayPal or COD\n            </div>\n          </div>\n          <div className='space-y-2'>\n            <Headset />\n            <div className='text-sm font-bold'>24/7 Support</div>\n            <div className='text-sm text-muted-foreground'>\n              Get support at any time\n            </div>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  );\n};\n\nexport default IconBoxes;\n"
  },
  {
    "path": "components/shared/checkout-steps.tsx",
    "content": "import React from 'react';\nimport { cn } from '@/lib/utils';\n\nconst CheckoutSteps = ({ current = 0 }) => {\n  return (\n    <div className='flex-between flex-col md:flex-row space-x-2 space-y-2 mb-10'>\n      {['User Login', 'Shipping Address', 'Payment Method', 'Place Order'].map(\n        (step, index) => (\n          <React.Fragment key={step}>\n            <div\n              className={cn(\n                'p-2 w-56 rounded-full text-center text-sm',\n                index === current ? 'bg-secondary' : ''\n              )}\n            >\n              {step}\n            </div>\n            {step !== 'Place Order' && (\n              <hr className='w-16 border-t border-gray-300 mx-2' />\n            )}\n          </React.Fragment>\n        )\n      )}\n    </div>\n  );\n};\n\nexport default CheckoutSteps;\n"
  },
  {
    "path": "components/shared/delete-dialog.tsx",
    "content": "'use client';\nimport { useState } from 'react';\nimport { useTransition } from 'react';\nimport { useToast } from '@/hooks/use-toast';\nimport { Button } from '../ui/button';\nimport {\n  AlertDialog,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from '../ui/alert-dialog';\n\nconst DeleteDialog = ({\n  id,\n  action,\n}: {\n  id: string;\n  action: (id: string) => Promise<{ success: boolean; message: string }>;\n}) => {\n  const [open, setOpen] = useState(false);\n  const [isPending, startTransition] = useTransition();\n  const { toast } = useToast();\n\n  const handleDeleteClick = () => {\n    startTransition(async () => {\n      const res = await action(id);\n\n      if (!res.success) {\n        toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n      } else {\n        setOpen(false);\n        toast({\n          description: res.message,\n        });\n      }\n    });\n  };\n\n  return (\n    <AlertDialog open={open} onOpenChange={setOpen}>\n      <AlertDialogTrigger asChild>\n        <Button size='sm' variant='destructive' className='ml-2'>\n          Delete\n        </Button>\n      </AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>\n          <AlertDialogDescription>\n            This action cannot be undone\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <Button\n            variant='destructive'\n            size='sm'\n            disabled={isPending}\n            onClick={handleDeleteClick}\n          >\n            {isPending ? 'Deleting...' : 'Delete'}\n          </Button>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n};\n\nexport default DeleteDialog;\n"
  },
  {
    "path": "components/shared/header/category-drawer.tsx",
    "content": "import { Button } from '@/components/ui/button';\nimport {\n  Drawer,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerTitle,\n  DrawerTrigger,\n} from '@/components/ui/drawer';\nimport { getAllCategories } from '@/lib/actions/product.actions';\nimport { MenuIcon } from 'lucide-react';\nimport Link from 'next/link';\n\nconst CategoryDrawer = async () => {\n  const categories = await getAllCategories();\n\n  return (\n    <Drawer direction='left'>\n      <DrawerTrigger asChild>\n        <Button variant='outline'>\n          <MenuIcon />\n        </Button>\n      </DrawerTrigger>\n      <DrawerContent className='h-full max-w-sm'>\n        <DrawerHeader>\n          <DrawerTitle>Select a category</DrawerTitle>\n          <div className='space-y-1 mt-4'>\n            {categories.map((x) => (\n              <Button\n                variant='ghost'\n                className='w-full justify-start'\n                key={x.category}\n                asChild\n              >\n                <DrawerClose asChild>\n                  <Link href={`/search?category=${x.category}`}>\n                    {x.category} ({x._count})\n                  </Link>\n                </DrawerClose>\n              </Button>\n            ))}\n          </div>\n        </DrawerHeader>\n      </DrawerContent>\n    </Drawer>\n  );\n};\n\nexport default CategoryDrawer;\n"
  },
  {
    "path": "components/shared/header/index.tsx",
    "content": "import Image from 'next/image';\nimport Link from 'next/link';\nimport { APP_NAME } from '@/lib/constants';\nimport Menu from './menu';\nimport CategoryDrawer from './category-drawer';\nimport Search from './search';\n\nconst Header = () => {\n  return (\n    <header className='w-full border-b'>\n      <div className='wrapper flex-between'>\n        <div className='flex-start'>\n          <CategoryDrawer />\n          <Link href='/' className='flex-start ml-4'>\n            <Image\n              src='/images/logo.svg'\n              alt={`${APP_NAME} logo`}\n              height={48}\n              width={48}\n              priority={true}\n            />\n            <span className='hidden lg:block font-bold text-2xl ml-3'>\n              {APP_NAME}\n            </span>\n          </Link>\n        </div>\n        <div className='hidden md:block'>\n          <Search />\n        </div>\n        <Menu />\n      </div>\n    </header>\n  );\n};\n\nexport default Header;\n"
  },
  {
    "path": "components/shared/header/menu.tsx",
    "content": "import { Button } from '@/components/ui/button';\nimport ModeToggle from './mode-toggle';\nimport Link from 'next/link';\nimport { EllipsisVertical, ShoppingCart } from 'lucide-react';\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,\n  SheetTitle,\n  SheetTrigger,\n} from '@/components/ui/sheet';\nimport UserButton from './user-button';\n\nconst Menu = () => {\n  return (\n    <div className='flex justify-end gap-3'>\n      <nav className='hidden md:flex w-full max-w-xs gap-1'>\n        <ModeToggle />\n        <Button asChild variant='ghost'>\n          <Link href='/cart'>\n            <ShoppingCart /> Cart\n          </Link>\n        </Button>\n        <UserButton />\n      </nav>\n      <nav className='md:hidden'>\n        <Sheet>\n          <SheetTrigger className='align-middle'>\n            <EllipsisVertical />\n          </SheetTrigger>\n          <SheetContent className='flex flex-col items-start'>\n            <SheetTitle>Menu</SheetTitle>\n            <ModeToggle />\n            <Button asChild variant='ghost'>\n              <Link href='/cart'>\n                <ShoppingCart /> Cart\n              </Link>\n            </Button>\n            <UserButton />\n            <SheetDescription></SheetDescription>\n          </SheetContent>\n        </Sheet>\n      </nav>\n    </div>\n  );\n};\n\nexport default Menu;\n"
  },
  {
    "path": "components/shared/header/mode-toggle.tsx",
    "content": "'use client';\nimport { useState, useEffect } from 'react';\nimport { Button } from '@/components/ui/button';\nimport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuContent,\n  DropdownMenuCheckboxItem,\n} from '@/components/ui/dropdown-menu';\nimport { useTheme } from 'next-themes';\nimport { SunIcon, MoonIcon, SunMoon } from 'lucide-react';\n\nconst ModeToggle = () => {\n  const [mounted, setMounted] = useState(false);\n  const { theme, setTheme } = useTheme();\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  if (!mounted) {\n    return null;\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant='ghost'\n          className='focus-visible:ring-0 focus-visible:ring-offset-0'\n        >\n          {theme === 'system' ? (\n            <SunMoon />\n          ) : theme === 'dark' ? (\n            <MoonIcon />\n          ) : (\n            <SunIcon />\n          )}\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent>\n        <DropdownMenuLabel>Appearance</DropdownMenuLabel>\n        <DropdownMenuSeparator />\n        <DropdownMenuCheckboxItem\n          checked={theme === 'system'}\n          onClick={() => setTheme('system')}\n        >\n          System\n        </DropdownMenuCheckboxItem>\n        <DropdownMenuCheckboxItem\n          checked={theme === 'dark'}\n          onClick={() => setTheme('dark')}\n        >\n          Dark\n        </DropdownMenuCheckboxItem>\n        <DropdownMenuCheckboxItem\n          checked={theme === 'light'}\n          onClick={() => setTheme('light')}\n        >\n          Light\n        </DropdownMenuCheckboxItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n};\n\nexport default ModeToggle;\n"
  },
  {
    "path": "components/shared/header/search.tsx",
    "content": "import { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from '@/components/ui/select';\nimport { getAllCategories } from '@/lib/actions/product.actions';\nimport { SearchIcon } from 'lucide-react';\n\nconst Search = async () => {\n  const categories = await getAllCategories();\n\n  return (\n    <form action='/search' method='GET'>\n      <div className='flex w-full max-w-sm items-center space-x-2'>\n        <Select name='category'>\n          <SelectTrigger className='w-[180px]'>\n            <SelectValue placeholder='All' />\n          </SelectTrigger>\n          <SelectContent>\n            <SelectItem key='All' value='all'>\n              All\n            </SelectItem>\n            {categories.map((x) => (\n              <SelectItem key={x.category} value={x.category}>\n                {x.category}\n              </SelectItem>\n            ))}\n          </SelectContent>\n        </Select>\n        <Input\n          name='q'\n          type='text'\n          placeholder='Search...'\n          className='md:w-[100px] lg:w-[300px]'\n        />\n        <Button>\n          <SearchIcon />\n        </Button>\n      </div>\n    </form>\n  );\n};\n\nexport default Search;\n"
  },
  {
    "path": "components/shared/header/user-button.tsx",
    "content": "import Link from 'next/link';\nimport { auth } from '@/auth';\nimport { signOutUser } from '@/lib/actions/user.actions';\nimport { Button } from '@/components/ui/button';\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { UserIcon } from 'lucide-react';\n\nconst UserButton = async () => {\n  const session = await auth();\n\n  if (!session) {\n    return (\n      <Button asChild>\n        <Link href='/sign-in'>\n          <UserIcon /> Sign In\n        </Link>\n      </Button>\n    );\n  }\n\n  const firstInitial = session.user?.name?.charAt(0).toUpperCase() ?? 'U';\n\n  return (\n    <div className='flex gap-2 items-center'>\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <div className='flex items-center'>\n            <Button\n              variant='ghost'\n              className='relativee w-8 h-8 rounded-full ml-2 flex items-center justify-center bg-gray-200'\n            >\n              {firstInitial}\n            </Button>\n          </div>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent className='w-56' align='end' forceMount>\n          <DropdownMenuLabel className='font-normal'>\n            <div className='flex flex-col space-y-1'>\n              <div className='text-sm font-medium leading-none'>\n                {session.user?.name}\n              </div>\n              <div className='text-sm text-muted-foreground leading-none'>\n                {session.user?.email}\n              </div>\n            </div>\n          </DropdownMenuLabel>\n\n          <DropdownMenuItem>\n            <Link href='/user/profile' className='w-full'>\n              User Profile\n            </Link>\n          </DropdownMenuItem>\n          <DropdownMenuItem>\n            <Link href='/user/orders' className='w-full'>\n              Order History\n            </Link>\n          </DropdownMenuItem>\n\n          {session?.user?.role === 'admin' && (\n            <DropdownMenuItem>\n              <Link href='/admin/overview' className='w-full'>\n                Admin\n              </Link>\n            </DropdownMenuItem>\n          )}\n\n          <DropdownMenuItem className='p-0 mb-1'>\n            <form action={signOutUser} className='w-full'>\n              <Button\n                className='w-full py-4 px-2 h-4 justify-start'\n                variant='ghost'\n              >\n                Sign Out\n              </Button>\n            </form>\n          </DropdownMenuItem>\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  );\n};\n\nexport default UserButton;\n"
  },
  {
    "path": "components/shared/pagination.tsx",
    "content": "'use client';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { Button } from '../ui/button';\nimport { formUrlQuery } from '@/lib/utils';\n\ntype PaginationProps = {\n  page: number | string;\n  totalPages: number;\n  urlParamName?: string;\n};\n\nconst Pagination = ({ page, totalPages, urlParamName }: PaginationProps) => {\n  const router = useRouter();\n  const searchParams = useSearchParams();\n\n  const handleClick = (btnType: string) => {\n    const pageValue = btnType === 'next' ? Number(page) + 1 : Number(page) - 1;\n    const newUrl = formUrlQuery({\n      params: searchParams.toString(),\n      key: urlParamName || 'page',\n      value: pageValue.toString(),\n    });\n\n    router.push(newUrl);\n  };\n\n  return (\n    <div className='flex gap-2'>\n      <Button\n        size='lg'\n        variant='outline'\n        className='w-28'\n        disabled={Number(page) <= 1}\n        onClick={() => handleClick('prev')}\n      >\n        Previous\n      </Button>\n      <Button\n        size='lg'\n        variant='outline'\n        className='w-28'\n        disabled={Number(page) >= totalPages}\n        onClick={() => handleClick('next')}\n      >\n        Next\n      </Button>\n    </div>\n  );\n};\n\nexport default Pagination;\n"
  },
  {
    "path": "components/shared/product/add-to-cart.tsx",
    "content": "'use client';\nimport { Button } from '@/components/ui/button';\nimport { useRouter } from 'next/navigation';\nimport { Plus, Minus, Loader } from 'lucide-react';\nimport { Cart, CartItem } from '@/types';\nimport { useToast } from '@/hooks/use-toast';\nimport { ToastAction } from '@/components/ui/toast';\nimport { addItemToCart, removeItemFromCart } from '@/lib/actions/cart.actions';\nimport { useTransition } from 'react';\n\nconst AddToCart = ({ cart, item }: { cart?: Cart; item: CartItem }) => {\n  const router = useRouter();\n  const { toast } = useToast();\n\n  const [isPending, startTransition] = useTransition();\n\n  const handleAddToCart = async () => {\n    startTransition(async () => {\n      const res = await addItemToCart(item);\n\n      if (!res.success) {\n        toast({\n          variant: 'destructive',\n          description: res.message,\n        });\n        return;\n      }\n\n      // Handle success add to cart\n      toast({\n        description: res.message,\n        action: (\n          <ToastAction\n            className='bg-primary text-white hover:bg-gray-800'\n            altText='Go To Cart'\n            onClick={() => router.push('/cart')}\n          >\n            Go To Cart\n          </ToastAction>\n        ),\n      });\n    });\n  };\n\n  // Handle remove from cart\n  const handleRemoveFromCart = async () => {\n    startTransition(async () => {\n      const res = await removeItemFromCart(item.productId);\n\n      toast({\n        variant: res.success ? 'default' : 'destructive',\n        description: res.message,\n      });\n\n      return;\n    });\n  };\n\n  // Check if item is in cart\n  const existItem =\n    cart && cart.items.find((x) => x.productId === item.productId);\n\n  return existItem ? (\n    <div>\n      <Button type='button' variant='outline' onClick={handleRemoveFromCart}>\n        {isPending ? (\n          <Loader className='w-4 h-4 animate-spin' />\n        ) : (\n          <Minus className='w-4 h-4' />\n        )}\n      </Button>\n      <span className='px-2'>{existItem.qty}</span>\n      <Button type='button' variant='outline' onClick={handleAddToCart}>\n        {isPending ? (\n          <Loader className='w-4 h-4 animate-spin' />\n        ) : (\n          <Plus className='w-4 h-4' />\n        )}\n      </Button>\n    </div>\n  ) : (\n    <Button className='w-full' type='button' onClick={handleAddToCart}>\n      {isPending ? (\n        <Loader className='w-4 h-4 animate-spin' />\n      ) : (\n        <Plus className='w-4 h-4' />\n      )}{' '}\n      Add To Cart\n    </Button>\n  );\n};\n\nexport default AddToCart;\n"
  },
  {
    "path": "components/shared/product/product-card.tsx",
    "content": "import Link from 'next/link';\nimport Image from 'next/image';\nimport { Card, CardContent, CardHeader } from '@/components/ui/card';\nimport ProductPrice from './product-price';\nimport { Product } from '@/types';\nimport Rating from './rating';\n\nconst ProductCard = ({ product }: { product: Product }) => {\n  return (\n    <Card className='w-full max-w-sm'>\n      <CardHeader className='p-0 items-center'>\n        <Link href={`/product/${product.slug}`}>\n          <Image\n            src={product.images[0]}\n            alt={product.name}\n            height={300}\n            width={300}\n            priority={true}\n          />\n        </Link>\n      </CardHeader>\n      <CardContent className='p-4 grid gap-4'>\n        <div className='text-xs'>{product.brand}</div>\n        <Link href={`/product/${product.slug}`}>\n          <h2 className='text-sm font-medium'>{product.name}</h2>\n        </Link>\n        <div className='flex-between gap-4'>\n          <Rating value={Number(product.rating)} />\n          {product.stock > 0 ? (\n            <ProductPrice value={Number(product.price)} />\n          ) : (\n            <p className='text-destructive'>Out Of Stock</p>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n};\n\nexport default ProductCard;\n"
  },
  {
    "path": "components/shared/product/product-carousel.tsx",
    "content": "'use client';\n\nimport {\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselNext,\n  CarouselPrevious,\n} from '@/components/ui/carousel';\nimport { Product } from '@/types';\nimport Autoplay from 'embla-carousel-autoplay';\nimport Link from 'next/link';\nimport Image from 'next/image';\n\nconst ProductCarousel = ({ data }: { data: Product[] }) => {\n  return (\n    <Carousel\n      className='w-full mb-12'\n      opts={{\n        loop: true,\n      }}\n      plugins={[\n        Autoplay({\n          delay: 10000,\n          stopOnInteraction: true,\n          stopOnMouseEnter: true,\n        }),\n      ]}\n    >\n      <CarouselContent>\n        {data.map((product: Product) => (\n          <CarouselItem key={product.id}>\n            <Link href={`/product/${product.slug}`}>\n              <div className='relative mx-auto'>\n                <Image\n                  src={product.banner!}\n                  alt={product.name}\n                  height='0'\n                  width='0'\n                  sizes='100vw'\n                  className='w-full h-auto'\n                />\n                <div className='absolute inset-0 flex items-end justify-center'>\n                  <h2 className='bg-gray-900 bg-opacity-50 text-2xl font-bold px-2 text-white'>\n                    {product.name}\n                  </h2>\n                </div>\n              </div>\n            </Link>\n          </CarouselItem>\n        ))}\n      </CarouselContent>\n      <CarouselPrevious />\n      <CarouselNext />\n    </Carousel>\n  );\n};\n\nexport default ProductCarousel;\n"
  },
  {
    "path": "components/shared/product/product-images.tsx",
    "content": "'use client';\nimport { useState } from 'react';\nimport Image from 'next/image';\nimport { cn } from '@/lib/utils';\n\nconst ProductImages = ({ images }: { images: string[] }) => {\n  const [current, setCurrent] = useState(0);\n\n  return (\n    <div className='space-y-4'>\n      <Image\n        src={images[current]}\n        alt='product image'\n        width={1000}\n        height={1000}\n        className='min-h-[300px] object-cover object-center'\n      />\n      <div className='flex'>\n        {images.map((image, index) => (\n          <div\n            key={image}\n            onClick={() => setCurrent(index)}\n            className={cn(\n              'border mr-2 cursor-pointer hover:border-orange-600',\n              current === index && 'border-orange-500'\n            )}\n          >\n            <Image src={image} alt='image' width={100} height={100} />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n};\n\nexport default ProductImages;\n"
  },
  {
    "path": "components/shared/product/product-list.tsx",
    "content": "import ProductCard from './product-card';\nimport { Product } from '@/types';\n\nconst ProductList = ({\n  data,\n  title,\n  limit,\n}: {\n  data: Product[];\n  title?: string;\n  limit?: number;\n}) => {\n  const limitedData = limit ? data.slice(0, limit) : data;\n\n  return (\n    <div className='my-10'>\n      <h2 className='h2-bold mb-4'>{title}</h2>\n      {data.length > 0 ? (\n        <div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4'>\n          {limitedData.map((product: Product) => (\n            <ProductCard key={product.slug} product={product} />\n          ))}\n        </div>\n      ) : (\n        <div>\n          <p>No products found</p>\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport default ProductList;\n"
  },
  {
    "path": "components/shared/product/product-price.tsx",
    "content": "import { cn } from '@/lib/utils';\n\nconst ProductPrice = ({\n  value,\n  className,\n}: {\n  value: number;\n  className?: string;\n}) => {\n  // Ensure two decimal places\n  const stringValue = value.toFixed(2);\n  // Get the int/float\n  const [intValue, floatValue] = stringValue.split('.');\n\n  return (\n    <p className={cn('text-2xl', className)}>\n      <span className='text-xs align-super'>$</span>\n      {intValue}\n      <span className='text-xs align-super'>.{floatValue}</span>\n    </p>\n  );\n};\n\nexport default ProductPrice;\n"
  },
  {
    "path": "components/shared/product/rating.tsx",
    "content": "const Rating = ({ value, caption }: { value: number; caption?: string }) => {\n  const Full = () => (\n    <svg\n      xmlns='http://www.w3.org/2000/svg'\n      className='text-yellow-500 w-5 h-auto fill-current'\n      viewBox='0 0 16 16'\n    >\n      <path d='M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z' />\n    </svg>\n  );\n  const Half = () => (\n    <svg\n      xmlns='http://www.w3.org/2000/svg'\n      className='text-yellow-500 w-5 h-auto fill-current'\n      viewBox='0 0 16 16'\n    >\n      <path d='M5.354 5.119 7.538.792A.516.516 0 0 1 8 .5c.183 0 .366.097.465.292l2.184 4.327 4.898.696A.537.537 0 0 1 16 6.32a.548.548 0 0 1-.17.445l-3.523 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256a.52.52 0 0 1-.146.05c-.342.06-.668-.254-.6-.642l.83-4.73L.173 6.765a.55.55 0 0 1-.172-.403.58.58 0 0 1 .085-.302.513.513 0 0 1 .37-.245l4.898-.696zM8 12.027a.5.5 0 0 1 .232.056l3.686 1.894-.694-3.957a.565.565 0 0 1 .162-.505l2.907-2.77-4.052-.576a.525.525 0 0 1-.393-.288L8.001 2.223 8 2.226v9.8z' />\n    </svg>\n  );\n  const Empty = () => (\n    <svg\n      xmlns='http://www.w3.org/2000/svg'\n      className='text-yellow-500 w-5 h-auto fill-current'\n      viewBox='0 0 16 16'\n    >\n      <path d='M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z' />\n    </svg>\n  );\n\n  return (\n    <div className='flex gap-2'>\n      <div className='flex gap-1'>\n        {value >= 1 ? <Full /> : value >= 0.5 ? <Half /> : <Empty />}\n        {value >= 2 ? <Full /> : value >= 1.5 ? <Half /> : <Empty />}\n        {value >= 3 ? <Full /> : value >= 2.5 ? <Half /> : <Empty />}\n        {value >= 4 ? <Full /> : value >= 3.5 ? <Half /> : <Empty />}\n        {value >= 5 ? <Full /> : value >= 4.5 ? <Half /> : <Empty />}\n      </div>\n\n      {caption && <span className='text-sm'>{caption}</span>}\n    </div>\n  );\n};\nexport default Rating;\n"
  },
  {
    "path": "components/ui/alert-dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants } from \"@/components/ui/button\"\n\nconst AlertDialog = AlertDialogPrimitive.Root\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger\n\nconst AlertDialogPortal = AlertDialogPrimitive.Portal\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n))\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName\n\nconst AlertDialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogHeader.displayName = \"AlertDialogHeader\"\n\nconst AlertDialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nAlertDialogFooter.displayName = \"AlertDialogFooter\"\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold\", className)}\n    {...props}\n  />\n))\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nAlertDialogDescription.displayName =\n  AlertDialogPrimitive.Description.displayName\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action\n    ref={ref}\n    className={cn(buttonVariants(), className)}\n    {...props}\n  />\n))\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(\n      buttonVariants({ variant: \"outline\" }),\n      \"mt-2 sm:mt-0\",\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName\n\nexport {\n  AlertDialog,\n  AlertDialogPortal,\n  AlertDialogOverlay,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel,\n}\n"
  },
  {
    "path": "components/ui/badge.tsx",
    "content": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst badgeVariants = cva(\n  \"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n        secondary:\n          \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        destructive:\n          \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n        outline: \"text-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nexport interface BadgeProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n  return (\n    <div className={cn(badgeVariants({ variant }), className)} {...props} />\n  )\n}\n\nexport { Badge, badgeVariants }\n"
  },
  {
    "path": "components/ui/button.tsx",
    "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n        outline:\n          \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-10 px-4 py-2\",\n        sm: \"h-9 rounded-md px-3\",\n        lg: \"h-11 rounded-md px-8\",\n        icon: \"h-10 w-10\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\"\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nButton.displayName = \"Button\"\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "components/ui/card.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"rounded-lg border bg-card text-card-foreground shadow-sm\",\n      className\n    )}\n    {...props}\n  />\n))\nCard.displayName = \"Card\"\n\nconst CardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n    {...props}\n  />\n))\nCardHeader.displayName = \"CardHeader\"\n\nconst CardTitle = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"text-2xl font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n))\nCardTitle.displayName = \"CardTitle\"\n\nconst CardDescription = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nCardDescription.displayName = \"CardDescription\"\n\nconst CardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n))\nCardContent.displayName = \"CardContent\"\n\nconst CardFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center p-6 pt-0\", className)}\n    {...props}\n  />\n))\nCardFooter.displayName = \"CardFooter\"\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }\n"
  },
  {
    "path": "components/ui/carousel.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from \"embla-carousel-react\"\nimport { ArrowLeft, ArrowRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ntype CarouselProps = {\n  opts?: CarouselOptions\n  plugins?: CarouselPlugin\n  orientation?: \"horizontal\" | \"vertical\"\n  setApi?: (api: CarouselApi) => void\n}\n\ntype CarouselContextProps = {\n  carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n  api: ReturnType<typeof useEmblaCarousel>[1]\n  scrollPrev: () => void\n  scrollNext: () => void\n  canScrollPrev: boolean\n  canScrollNext: boolean\n} & CarouselProps\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n  const context = React.useContext(CarouselContext)\n\n  if (!context) {\n    throw new Error(\"useCarousel must be used within a <Carousel />\")\n  }\n\n  return context\n}\n\nconst Carousel = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n  (\n    {\n      orientation = \"horizontal\",\n      opts,\n      setApi,\n      plugins,\n      className,\n      children,\n      ...props\n    },\n    ref\n  ) => {\n    const [carouselRef, api] = useEmblaCarousel(\n      {\n        ...opts,\n        axis: orientation === \"horizontal\" ? \"x\" : \"y\",\n      },\n      plugins\n    )\n    const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n    const [canScrollNext, setCanScrollNext] = React.useState(false)\n\n    const onSelect = React.useCallback((api: CarouselApi) => {\n      if (!api) {\n        return\n      }\n\n      setCanScrollPrev(api.canScrollPrev())\n      setCanScrollNext(api.canScrollNext())\n    }, [])\n\n    const scrollPrev = React.useCallback(() => {\n      api?.scrollPrev()\n    }, [api])\n\n    const scrollNext = React.useCallback(() => {\n      api?.scrollNext()\n    }, [api])\n\n    const handleKeyDown = React.useCallback(\n      (event: React.KeyboardEvent<HTMLDivElement>) => {\n        if (event.key === \"ArrowLeft\") {\n          event.preventDefault()\n          scrollPrev()\n        } else if (event.key === \"ArrowRight\") {\n          event.preventDefault()\n          scrollNext()\n        }\n      },\n      [scrollPrev, scrollNext]\n    )\n\n    React.useEffect(() => {\n      if (!api || !setApi) {\n        return\n      }\n\n      setApi(api)\n    }, [api, setApi])\n\n    React.useEffect(() => {\n      if (!api) {\n        return\n      }\n\n      onSelect(api)\n      api.on(\"reInit\", onSelect)\n      api.on(\"select\", onSelect)\n\n      return () => {\n        api?.off(\"select\", onSelect)\n      }\n    }, [api, onSelect])\n\n    return (\n      <CarouselContext.Provider\n        value={{\n          carouselRef,\n          api: api,\n          opts,\n          orientation:\n            orientation || (opts?.axis === \"y\" ? \"vertical\" : \"horizontal\"),\n          scrollPrev,\n          scrollNext,\n          canScrollPrev,\n          canScrollNext,\n        }}\n      >\n        <div\n          ref={ref}\n          onKeyDownCapture={handleKeyDown}\n          className={cn(\"relative\", className)}\n          role=\"region\"\n          aria-roledescription=\"carousel\"\n          {...props}\n        >\n          {children}\n        </div>\n      </CarouselContext.Provider>\n    )\n  }\n)\nCarousel.displayName = \"Carousel\"\n\nconst CarouselContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const { carouselRef, orientation } = useCarousel()\n\n  return (\n    <div ref={carouselRef} className=\"overflow-hidden\">\n      <div\n        ref={ref}\n        className={cn(\n          \"flex\",\n          orientation === \"horizontal\" ? \"-ml-4\" : \"-mt-4 flex-col\",\n          className\n        )}\n        {...props}\n      />\n    </div>\n  )\n})\nCarouselContent.displayName = \"CarouselContent\"\n\nconst CarouselItem = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const { orientation } = useCarousel()\n\n  return (\n    <div\n      ref={ref}\n      role=\"group\"\n      aria-roledescription=\"slide\"\n      className={cn(\n        \"min-w-0 shrink-0 grow-0 basis-full\",\n        orientation === \"horizontal\" ? \"pl-4\" : \"pt-4\",\n        className\n      )}\n      {...props}\n    />\n  )\n})\nCarouselItem.displayName = \"CarouselItem\"\n\nconst CarouselPrevious = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<typeof Button>\n>(({ className, variant = \"outline\", size = \"icon\", ...props }, ref) => {\n  const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n\n  return (\n    <Button\n      ref={ref}\n      variant={variant}\n      size={size}\n      className={cn(\n        \"absolute  h-8 w-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"-left-12 top-1/2 -translate-y-1/2\"\n          : \"-top-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className\n      )}\n      disabled={!canScrollPrev}\n      onClick={scrollPrev}\n      {...props}\n    >\n      <ArrowLeft className=\"h-4 w-4\" />\n      <span className=\"sr-only\">Previous slide</span>\n    </Button>\n  )\n})\nCarouselPrevious.displayName = \"CarouselPrevious\"\n\nconst CarouselNext = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<typeof Button>\n>(({ className, variant = \"outline\", size = \"icon\", ...props }, ref) => {\n  const { orientation, scrollNext, canScrollNext } = useCarousel()\n\n  return (\n    <Button\n      ref={ref}\n      variant={variant}\n      size={size}\n      className={cn(\n        \"absolute h-8 w-8 rounded-full\",\n        orientation === \"horizontal\"\n          ? \"-right-12 top-1/2 -translate-y-1/2\"\n          : \"-bottom-12 left-1/2 -translate-x-1/2 rotate-90\",\n        className\n      )}\n      disabled={!canScrollNext}\n      onClick={scrollNext}\n      {...props}\n    >\n      <ArrowRight className=\"h-4 w-4\" />\n      <span className=\"sr-only\">Next slide</span>\n    </Button>\n  )\n})\nCarouselNext.displayName = \"CarouselNext\"\n\nexport {\n  type CarouselApi,\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselPrevious,\n  CarouselNext,\n}\n"
  },
  {
    "path": "components/ui/checkbox.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Checkbox = React.forwardRef<\n  React.ElementRef<typeof CheckboxPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <CheckboxPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n      className\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator\n      className={cn(\"flex items-center justify-center text-current\")}\n    >\n      <Check className=\"h-4 w-4\" />\n    </CheckboxPrimitive.Indicator>\n  </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n"
  },
  {
    "path": "components/ui/dialog.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Dialog = DialogPrimitive.Root\n\nconst DialogTrigger = DialogPrimitive.Trigger\n\nconst DialogPortal = DialogPrimitive.Portal\n\nconst DialogClose = DialogPrimitive.Close\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    ref={ref}\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName\n\nconst DialogContent = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DialogPortal>\n    <DialogOverlay />\n    <DialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <DialogPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </DialogPrimitive.Close>\n    </DialogPrimitive.Content>\n  </DialogPortal>\n))\nDialogContent.displayName = DialogPrimitive.Content.displayName\n\nconst DialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-1.5 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogHeader.displayName = \"DialogHeader\"\n\nconst DialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nDialogFooter.displayName = \"DialogFooter\"\n\nconst DialogTitle = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n))\nDialogTitle.displayName = DialogPrimitive.Title.displayName\n\nconst DialogDescription = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog,\n  DialogPortal,\n  DialogOverlay,\n  DialogClose,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n}\n"
  },
  {
    "path": "components/ui/drawer.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Drawer as DrawerPrimitive } from \"vaul\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Drawer = ({\n  shouldScaleBackground = true,\n  ...props\n}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (\n  <DrawerPrimitive.Root\n    shouldScaleBackground={shouldScaleBackground}\n    {...props}\n  />\n)\nDrawer.displayName = \"Drawer\"\n\nconst DrawerTrigger = DrawerPrimitive.Trigger\n\nconst DrawerPortal = DrawerPrimitive.Portal\n\nconst DrawerClose = DrawerPrimitive.Close\n\nconst DrawerOverlay = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <DrawerPrimitive.Overlay\n    ref={ref}\n    className={cn(\"fixed inset-0 z-50 bg-black/80\", className)}\n    {...props}\n  />\n))\nDrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName\n\nconst DrawerContent = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <DrawerPortal>\n    <DrawerOverlay />\n    <DrawerPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted\" />\n      {children}\n    </DrawerPrimitive.Content>\n  </DrawerPortal>\n))\nDrawerContent.displayName = \"DrawerContent\"\n\nconst DrawerHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"grid gap-1.5 p-4 text-center sm:text-left\", className)}\n    {...props}\n  />\n)\nDrawerHeader.displayName = \"DrawerHeader\"\n\nconst DrawerFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)}\n    {...props}\n  />\n)\nDrawerFooter.displayName = \"DrawerFooter\"\n\nconst DrawerTitle = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <DrawerPrimitive.Title\n    ref={ref}\n    className={cn(\n      \"text-lg font-semibold leading-none tracking-tight\",\n      className\n    )}\n    {...props}\n  />\n))\nDrawerTitle.displayName = DrawerPrimitive.Title.displayName\n\nconst DrawerDescription = React.forwardRef<\n  React.ElementRef<typeof DrawerPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <DrawerPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nDrawerDescription.displayName = DrawerPrimitive.Description.displayName\n\nexport {\n  Drawer,\n  DrawerPortal,\n  DrawerOverlay,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerContent,\n  DrawerHeader,\n  DrawerFooter,\n  DrawerTitle,\n  DrawerDescription,\n}\n"
  },
  {
    "path": "components/ui/dropdown-menu.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto\" />\n  </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n  DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuSubContent.displayName =\n  DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <DropdownMenuPrimitive.Portal>\n    <DropdownMenuPrimitive.Content\n      ref={ref}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className\n      )}\n      {...props}\n    />\n  </DropdownMenuPrimitive.Portal>\n))\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <DropdownMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n  DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"px-2 py-1.5 text-sm font-semibold\",\n      inset && \"pl-8\",\n      className\n    )}\n    {...props}\n  />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\n      {...props}\n    />\n  )\n}\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\"\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuGroup,\n  DropdownMenuPortal,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuRadioGroup,\n}\n"
  },
  {
    "path": "components/ui/form.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n  Controller,\n  ControllerProps,\n  FieldPath,\n  FieldValues,\n  FormProvider,\n  useFormContext,\n} from \"react-hook-form\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\n\nconst Form = FormProvider\n\ntype FormFieldContextValue<\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n> = {\n  name: TName\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n  {} as FormFieldContextValue\n)\n\nconst FormField = <\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>\n>({\n  ...props\n}: ControllerProps<TFieldValues, TName>) => {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  )\n}\n\nconst useFormField = () => {\n  const fieldContext = React.useContext(FormFieldContext)\n  const itemContext = React.useContext(FormItemContext)\n  const { getFieldState, formState } = useFormContext()\n\n  const fieldState = getFieldState(fieldContext.name, formState)\n\n  if (!fieldContext) {\n    throw new Error(\"useFormField should be used within <FormField>\")\n  }\n\n  const { id } = itemContext\n\n  return {\n    id,\n    name: fieldContext.name,\n    formItemId: `${id}-form-item`,\n    formDescriptionId: `${id}-form-item-description`,\n    formMessageId: `${id}-form-item-message`,\n    ...fieldState,\n  }\n}\n\ntype FormItemContextValue = {\n  id: string\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n  {} as FormItemContextValue\n)\n\nconst FormItem = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const id = React.useId()\n\n  return (\n    <FormItemContext.Provider value={{ id }}>\n      <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n    </FormItemContext.Provider>\n  )\n})\nFormItem.displayName = \"FormItem\"\n\nconst FormLabel = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  const { error, formItemId } = useFormField()\n\n  return (\n    <Label\n      ref={ref}\n      className={cn(error && \"text-destructive\", className)}\n      htmlFor={formItemId}\n      {...props}\n    />\n  )\n})\nFormLabel.displayName = \"FormLabel\"\n\nconst FormControl = React.forwardRef<\n  React.ElementRef<typeof Slot>,\n  React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()\n\n  return (\n    <Slot\n      ref={ref}\n      id={formItemId}\n      aria-describedby={\n        !error\n          ? `${formDescriptionId}`\n          : `${formDescriptionId} ${formMessageId}`\n      }\n      aria-invalid={!!error}\n      {...props}\n    />\n  )\n})\nFormControl.displayName = \"FormControl\"\n\nconst FormDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n  const { formDescriptionId } = useFormField()\n\n  return (\n    <p\n      ref={ref}\n      id={formDescriptionId}\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n})\nFormDescription.displayName = \"FormDescription\"\n\nconst FormMessage = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n  const { error, formMessageId } = useFormField()\n  const body = error ? String(error?.message) : children\n\n  if (!body) {\n    return null\n  }\n\n  return (\n    <p\n      ref={ref}\n      id={formMessageId}\n      className={cn(\"text-sm font-medium text-destructive\", className)}\n      {...props}\n    >\n      {body}\n    </p>\n  )\n})\nFormMessage.displayName = \"FormMessage\"\n\nexport {\n  useFormField,\n  Form,\n  FormItem,\n  FormLabel,\n  FormControl,\n  FormDescription,\n  FormMessage,\n  FormField,\n}\n"
  },
  {
    "path": "components/ui/input.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = \"Input\"\n\nexport { Input }\n"
  },
  {
    "path": "components/ui/label.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst labelVariants = cva(\n  \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\"\n)\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n    VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(labelVariants(), className)}\n    {...props}\n  />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"
  },
  {
    "path": "components/ui/radio-group.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\"\nimport { Circle } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst RadioGroup = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Root\n      className={cn(\"grid gap-2\", className)}\n      {...props}\n      ref={ref}\n    />\n  )\n})\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName\n\nconst RadioGroupItem = React.forwardRef<\n  React.ElementRef<typeof RadioGroupPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        \"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    >\n      <RadioGroupPrimitive.Indicator className=\"flex items-center justify-center\">\n        <Circle className=\"h-2.5 w-2.5 fill-current text-current\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n})\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "components/ui/select.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SelectPrimitive from \"@radix-ui/react-select\"\nimport { Check, ChevronDown, ChevronUp } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Select = SelectPrimitive.Root\n\nconst SelectGroup = SelectPrimitive.Group\n\nconst SelectValue = SelectPrimitive.Value\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDown className=\"h-4 w-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n))\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronUp className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollUpButton>\n))\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className\n    )}\n    {...props}\n  >\n    <ChevronDown className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollDownButton>\n))\nSelectScrollDownButton.displayName =\n  SelectPrimitive.ScrollDownButton.displayName\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        position === \"popper\" &&\n          \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n        className\n      )}\n      position={position}\n      {...props}\n    >\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          \"p-1\",\n          position === \"popper\" &&\n            \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\"\n        )}\n      >\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n))\nSelectContent.displayName = SelectPrimitive.Content.displayName\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\"py-1.5 pl-8 pr-2 text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nSelectLabel.displayName = SelectPrimitive.Label.displayName\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n))\nSelectItem.displayName = SelectPrimitive.Item.displayName\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n))\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton,\n}\n"
  },
  {
    "path": "components/ui/sheet.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Sheet = SheetPrimitive.Root\n\nconst SheetTrigger = SheetPrimitive.Trigger\n\nconst SheetClose = SheetPrimitive.Close\n\nconst SheetPortal = SheetPrimitive.Portal\n\nconst SheetOverlay = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName\n\nconst sheetVariants = cva(\n  \"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n  {\n    variants: {\n      side: {\n        top: \"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n        bottom:\n          \"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n        left: \"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n        right:\n          \"inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n      },\n    },\n    defaultVariants: {\n      side: \"right\",\n    },\n  }\n)\n\ninterface SheetContentProps\n  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n    VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Content>,\n  SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n  <SheetPortal>\n    <SheetOverlay />\n    <SheetPrimitive.Content\n      ref={ref}\n      className={cn(sheetVariants({ side }), className)}\n      {...props}\n    >\n      {children}\n      <SheetPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary\">\n        <X className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </SheetPrimitive.Close>\n    </SheetPrimitive.Content>\n  </SheetPortal>\n))\nSheetContent.displayName = SheetPrimitive.Content.displayName\n\nconst SheetHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className\n    )}\n    {...props}\n  />\n)\nSheetHeader.displayName = \"SheetHeader\"\n\nconst SheetFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className\n    )}\n    {...props}\n  />\n)\nSheetFooter.displayName = \"SheetFooter\"\n\nconst SheetTitle = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold text-foreground\", className)}\n    {...props}\n  />\n))\nSheetTitle.displayName = SheetPrimitive.Title.displayName\n\nconst SheetDescription = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nSheetDescription.displayName = SheetPrimitive.Description.displayName\n\nexport {\n  Sheet,\n  SheetPortal,\n  SheetOverlay,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n}\n"
  },
  {
    "path": "components/ui/table.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Table = React.forwardRef<\n  HTMLTableElement,\n  React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n  <div className=\"relative w-full overflow-auto\">\n    <table\n      ref={ref}\n      className={cn(\"w-full caption-bottom text-sm\", className)}\n      {...props}\n    />\n  </div>\n))\nTable.displayName = \"Table\"\n\nconst TableHeader = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n))\nTableHeader.displayName = \"TableHeader\"\n\nconst TableBody = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tbody\n    ref={ref}\n    className={cn(\"[&_tr:last-child]:border-0\", className)}\n    {...props}\n  />\n))\nTableBody.displayName = \"TableBody\"\n\nconst TableFooter = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tfoot\n    ref={ref}\n    className={cn(\n      \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n      className\n    )}\n    {...props}\n  />\n))\nTableFooter.displayName = \"TableFooter\"\n\nconst TableRow = React.forwardRef<\n  HTMLTableRowElement,\n  React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n  <tr\n    ref={ref}\n    className={cn(\n      \"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n      className\n    )}\n    {...props}\n  />\n))\nTableRow.displayName = \"TableRow\"\n\nconst TableHead = React.forwardRef<\n  HTMLTableCellElement,\n  React.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <th\n    ref={ref}\n    className={cn(\n      \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n      className\n    )}\n    {...props}\n  />\n))\nTableHead.displayName = \"TableHead\"\n\nconst TableCell = React.forwardRef<\n  HTMLTableCellElement,\n  React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <td\n    ref={ref}\n    className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)}\n    {...props}\n  />\n))\nTableCell.displayName = \"TableCell\"\n\nconst TableCaption = React.forwardRef<\n  HTMLTableCaptionElement,\n  React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n  <caption\n    ref={ref}\n    className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n))\nTableCaption.displayName = \"TableCaption\"\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n}\n"
  },
  {
    "path": "components/ui/textarea.tsx",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Textarea = React.forwardRef<\n  HTMLTextAreaElement,\n  React.ComponentProps<\"textarea\">\n>(({ className, ...props }, ref) => {\n  return (\n    <textarea\n      className={cn(\n        \"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm\",\n        className\n      )}\n      ref={ref}\n      {...props}\n    />\n  )\n})\nTextarea.displayName = \"Textarea\"\n\nexport { Textarea }\n"
  },
  {
    "path": "components/ui/toast.tsx",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Viewport>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Viewport\n    ref={ref}\n    className={cn(\n      \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n  \"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full\",\n  {\n    variants: {\n      variant: {\n        default: \"border bg-background text-foreground\",\n        destructive:\n          \"destructive group border-destructive bg-destructive text-destructive-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  }\n)\n\nconst Toast = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n    VariantProps<typeof toastVariants>\n>(({ className, variant, ...props }, ref) => {\n  return (\n    <ToastPrimitives.Root\n      ref={ref}\n      className={cn(toastVariants({ variant }), className)}\n      {...props}\n    />\n  )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Action>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Action\n    ref={ref}\n    className={cn(\n      \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive\",\n      className\n    )}\n    {...props}\n  />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Close>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Close\n    ref={ref}\n    className={cn(\n      \"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600\",\n      className\n    )}\n    toast-close=\"\"\n    {...props}\n  >\n    <X className=\"h-4 w-4\" />\n  </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Title>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Title\n    ref={ref}\n    className={cn(\"text-sm font-semibold\", className)}\n    {...props}\n  />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n  React.ElementRef<typeof ToastPrimitives.Description>,\n  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ className, ...props }, ref) => (\n  <ToastPrimitives.Description\n    ref={ref}\n    className={cn(\"text-sm opacity-90\", className)}\n    {...props}\n  />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n  type ToastProps,\n  type ToastActionElement,\n  ToastProvider,\n  ToastViewport,\n  Toast,\n  ToastTitle,\n  ToastDescription,\n  ToastClose,\n  ToastAction,\n}\n"
  },
  {
    "path": "components/ui/toaster.tsx",
    "content": "\"use client\"\n\nimport { useToast } from \"@/hooks/use-toast\"\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  ToastTitle,\n  ToastViewport,\n} from \"@/components/ui/toast\"\n\nexport function Toaster() {\n  const { toasts } = useToast()\n\n  return (\n    <ToastProvider>\n      {toasts.map(function ({ id, title, description, action, ...props }) {\n        return (\n          <Toast key={id} {...props}>\n            <div className=\"grid gap-1\">\n              {title && <ToastTitle>{title}</ToastTitle>}\n              {description && (\n                <ToastDescription>{description}</ToastDescription>\n              )}\n            </div>\n            {action}\n            <ToastClose />\n          </Toast>\n        )\n      })}\n      <ToastViewport />\n    </ToastProvider>\n  )\n}\n"
  },
  {
    "path": "components/view-all-products-button.tsx",
    "content": "import { Button } from './ui/button';\nimport Link from 'next/link';\n\nconst ViewAllProductsButton = () => {\n  return (\n    <div className='flex justify-center items-center my-8'>\n      <Button asChild className='px-8 py-4 text-lg font-semibold'>\n        <Link href='/search'>View All Products</Link>\n      </Button>\n    </div>\n  );\n};\n\nexport default ViewAllProductsButton;\n"
  },
  {
    "path": "components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"default\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.ts\",\n    \"css\": \"assets/styles/globals.css\",\n    \"baseColor\": \"slate\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"iconLibrary\": \"lucide\"\n}"
  },
  {
    "path": "db/prisma.ts",
    "content": "import { Pool, neonConfig } from '@neondatabase/serverless';\nimport { PrismaNeon } from '@prisma/adapter-neon';\nimport { PrismaClient } from '@prisma/client';\nimport ws from 'ws';\n\n// Sets up WebSocket connections, which enables Neon to use WebSocket communication.\nneonConfig.webSocketConstructor = ws;\nconst connectionString = `${process.env.DATABASE_URL}`;\n\n// Creates a new connection pool using the provided connection string, allowing multiple concurrent connections.\nconst pool = new Pool({ connectionString });\n\n// Instantiates the Prisma adapter using the Neon connection pool to handle the connection between Prisma and Neon.\nconst adapter = new PrismaNeon(pool);\n\n// Extends the PrismaClient with a custom result transformer to convert the price and rating fields to strings.\nexport const prisma = new PrismaClient({ adapter }).$extends({\n  result: {\n    product: {\n      price: {\n        compute(product) {\n          return product.price.toString();\n        },\n      },\n      rating: {\n        compute(product) {\n          return product.rating.toString();\n        },\n      },\n    },\n    cart: {\n      itemsPrice: {\n        needs: { itemsPrice: true },\n        compute(cart) {\n          return cart.itemsPrice.toString();\n        },\n      },\n      shippingPrice: {\n        needs: { shippingPrice: true },\n        compute(cart) {\n          return cart.shippingPrice.toString();\n        },\n      },\n      taxPrice: {\n        needs: { taxPrice: true },\n        compute(cart) {\n          return cart.taxPrice.toString();\n        },\n      },\n      totalPrice: {\n        needs: { totalPrice: true },\n        compute(cart) {\n          return cart.totalPrice.toString();\n        },\n      },\n    },\n    order: {\n      itemsPrice: {\n        needs: { itemsPrice: true },\n        compute(cart) {\n          return cart.itemsPrice.toString();\n        },\n      },\n      shippingPrice: {\n        needs: { shippingPrice: true },\n        compute(cart) {\n          return cart.shippingPrice.toString();\n        },\n      },\n      taxPrice: {\n        needs: { taxPrice: true },\n        compute(cart) {\n          return cart.taxPrice.toString();\n        },\n      },\n      totalPrice: {\n        needs: { totalPrice: true },\n        compute(cart) {\n          return cart.totalPrice.toString();\n        },\n      },\n    },\n    orderItem: {\n      price: {\n        compute(cart) {\n          return cart.price.toString();\n        },\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "db/sample-data.ts",
    "content": "const sampleData = {\r\n  users: [\r\n    {\r\n      name: 'John',\r\n      email: 'admin@example.com',\r\n      password: '123456',\r\n      role: 'admin',\r\n    },\r\n    {\r\n      name: 'Jane',\r\n      email: 'user@example.com',\r\n      password: '123456',\r\n      role: 'user',\r\n    },\r\n  ],\r\n  products: [\r\n    {\r\n      name: 'Polo Sporting Stretch Shirt',\r\n      slug: 'polo-sporting-stretch-shirt',\r\n      category: \"Men's Dress Shirts\",\r\n      description: 'Classic Polo style with modern comfort',\r\n      images: [\r\n        '/images/sample-products/p1-1.jpg',\r\n        '/images/sample-products/p1-2.jpg',\r\n      ],\r\n      price: 59.99,\r\n      brand: 'Polo',\r\n      rating: 4.5,\r\n      numReviews: 10,\r\n      stock: 5,\r\n      isFeatured: true,\r\n      banner: '/images/banner-1.jpg',\r\n    },\r\n    {\r\n      name: 'Brooks Brothers Long Sleeved Shirt',\r\n      slug: 'brooks-brothers-long-sleeved-shirt',\r\n      category: \"Men's Dress Shirts\",\r\n      description: 'Timeless style and premium comfort',\r\n      images: [\r\n        '/images/sample-products/p2-1.jpg',\r\n        '/images/sample-products/p2-2.jpg',\r\n      ],\r\n      price: 85.9,\r\n      brand: 'Brooks Brothers',\r\n      rating: 4.2,\r\n      numReviews: 8,\r\n      stock: 10,\r\n      isFeatured: true,\r\n      banner: '/images/banner-2.jpg',\r\n    },\r\n    {\r\n      name: 'Tommy Hilfiger Classic Fit Dress Shirt',\r\n      slug: 'tommy-hilfiger-classic-fit-dress-shirt',\r\n      category: \"Men's Dress Shirts\",\r\n      description: 'A perfect blend of sophistication and comfort',\r\n      images: [\r\n        '/images/sample-products/p3-1.jpg',\r\n        '/images/sample-products/p3-2.jpg',\r\n      ],\r\n      price: 99.95,\r\n      brand: 'Tommy Hilfiger',\r\n      rating: 4.9,\r\n      numReviews: 3,\r\n      stock: 0,\r\n      isFeatured: false,\r\n      banner: null,\r\n    },\r\n    {\r\n      name: 'Calvin Klein Slim Fit Stretch Shirt',\r\n      slug: 'calvin-klein-slim-fit-stretch-shirt',\r\n      category: \"Men's Dress Shirts\",\r\n      description: 'Streamlined design with flexible stretch fabric',\r\n      images: [\r\n        '/images/sample-products/p4-1.jpg',\r\n        '/images/sample-products/p4-2.jpg',\r\n      ],\r\n      price: 39.95,\r\n      brand: 'Calvin Klein',\r\n      rating: 3.6,\r\n      numReviews: 5,\r\n      stock: 10,\r\n      isFeatured: false,\r\n      banner: null,\r\n    },\r\n    {\r\n      name: 'Polo Ralph Lauren Oxford Shirt',\r\n      slug: 'polo-ralph-lauren-oxford-shirt',\r\n      category: \"Men's Dress Shirts\",\r\n      description: 'Iconic Polo design with refined oxford fabric',\r\n      images: [\r\n        '/images/sample-products/p5-1.jpg',\r\n        '/images/sample-products/p5-2.jpg',\r\n      ],\r\n      price: 79.99,\r\n      brand: 'Polo',\r\n      rating: 4.7,\r\n      numReviews: 18,\r\n      stock: 6,\r\n      isFeatured: false,\r\n      banner: null,\r\n    },\r\n    {\r\n      name: 'Polo Classic Pink Hoodie',\r\n      slug: 'polo-classic-pink-hoodie',\r\n      category: \"Men's Sweatshirts\",\r\n      description: 'Soft, stylish, and perfect for laid-back days',\r\n      images: [\r\n        '/images/sample-products/p6-1.jpg',\r\n        '/images/sample-products/p6-2.jpg',\r\n      ],\r\n      price: 99.99,\r\n      brand: 'Polo',\r\n      rating: 4.6,\r\n      numReviews: 12,\r\n      stock: 8,\r\n      isFeatured: true,\r\n      banner: null,\r\n    },\r\n  ],\r\n};\r\n\r\nexport default sampleData;\r\n"
  },
  {
    "path": "db/seed.ts",
    "content": "import { PrismaClient } from '@prisma/client';\nimport sampleData from './sample-data';\nimport { hash } from '@/lib/encrypt';\n\nasync function main() {\n  const prisma = new PrismaClient();\n  await prisma.product.deleteMany();\n  await prisma.account.deleteMany();\n  await prisma.session.deleteMany();\n  await prisma.verificationToken.deleteMany();\n  await prisma.user.deleteMany();\n\n  await prisma.product.createMany({ data: sampleData.products });\n  const users = [];\n  for (let i = 0; i < sampleData.users.length; i++) {\n    users.push({\n      ...sampleData.users[i],\n      password: await hash(sampleData.users[i].password),\n    });\n    console.log(\n      sampleData.users[i].password,\n      await hash(sampleData.users[i].password)\n    );\n  }\n  await prisma.user.createMany({ data: users });\n\n  console.log('Database seeded successfully!');\n}\n\nmain();\n"
  },
  {
    "path": "email/index.tsx",
    "content": "import { Resend } from 'resend';\nimport { SENDER_EMAIL, APP_NAME } from '@/lib/constants';\nimport { Order } from '@/types';\nimport dotenv from 'dotenv';\ndotenv.config();\n\nimport PurchaseReceiptEmail from './purchase-receipt';\n\nconst resend = new Resend(process.env.RESEND_API_KEY as string);\n\nexport const sendPurchaseReceipt = async ({ order }: { order: Order }) => {\n  await resend.emails.send({\n    from: `${APP_NAME} <${SENDER_EMAIL}>`,\n    to: order.user.email,\n    subject: `Order Confirmation ${order.id}`,\n    react: <PurchaseReceiptEmail order={order} />,\n  });\n};\n"
  },
  {
    "path": "email/purchase-receipt.tsx",
    "content": "import {\n  Body,\n  Column,\n  Container,\n  Head,\n  Heading,\n  Html,\n  Img,\n  Preview,\n  Row,\n  Section,\n  Tailwind,\n  Text,\n} from '@react-email/components';\nimport { Order } from '@/types';\nimport { formatCurrency } from '@/lib/utils';\nimport sampleData from '@/db/sample-data';\nrequire('dotenv').config();\n\nPurchaseReceiptEmail.PreviewProps = {\n  order: {\n    id: crypto.randomUUID(),\n    userId: '123',\n    user: {\n      name: 'John Doe',\n      email: 'test@test.com',\n    },\n    paymentMethod: 'Stripe',\n    shippingAddress: {\n      fullName: 'John Doe',\n      streetAddress: '123 Main st',\n      city: 'New York',\n      postalCode: '10001',\n      country: 'US',\n    },\n    createdAt: new Date(),\n    totalPrice: '100',\n    taxPrice: '10',\n    shippingPrice: '10',\n    itemsPrice: '80',\n    orderitems: sampleData.products.map((x) => ({\n      name: x.name,\n      orderId: '123',\n      productId: '123',\n      slug: x.slug,\n      qty: x.stock,\n      image: x.images[0],\n      price: x.price.toString(),\n    })),\n    isDelivered: true,\n    deliveredAt: new Date(),\n    isPaid: true,\n    paidAt: new Date(),\n    paymentResult: {\n      id: '123',\n      status: 'succeeded',\n      pricePaid: '100',\n      email_address: 'test@test.com',\n    },\n  },\n} satisfies OrderInformationProps;\n\nconst dateFormatter = new Intl.DateTimeFormat('en', { dateStyle: 'medium' });\n\ntype OrderInformationProps = {\n  order: Order;\n};\n\nexport default function PurchaseReceiptEmail({ order }: OrderInformationProps) {\n  return (\n    <Html>\n      <Preview>View order receipt</Preview>\n      <Tailwind>\n        <Head />\n        <Body className='font-sans bg-white'>\n          <Container className='max-w-xl'>\n            <Heading>Purchase Receipt</Heading>\n            <Section>\n              <Row>\n                <Column>\n                  <Text className='mb-0 mr-4 text-gray-500 whitespace-nowrap text-nowrap'>\n                    Order ID\n                  </Text>\n                  <Text className='mt-0 mr-4'>{order.id.toString()}</Text>\n                </Column>\n                <Column>\n                  <Text className='mb-0 mr-4 text-gray-500 whitespace-nowrap text-nowrap'>\n                    Purchase Date\n                  </Text>\n                  <Text className='mt-0 mr-4'>\n                    {dateFormatter.format(order.createdAt)}\n                  </Text>\n                </Column>\n                <Column>\n                  <Text className='mb-0 mr-4 text-gray-500 whitespace-nowrap text-nowrap'>\n                    Price Paid\n                  </Text>\n                  <Text className='mt-0 mr-4'>\n                    {formatCurrency(order.totalPrice)}\n                  </Text>\n                </Column>\n              </Row>\n            </Section>\n            <Section className='border border-solid border-gray-500 rounded-lg p-4 md:p-6 my-4'>\n              {order.orderitems.map((item) => (\n                <Row key={item.productId} className='mt-8'>\n                  <Column className='w-20'>\n                    <Img\n                      width='80'\n                      alt={item.name}\n                      className='rounded'\n                      src={\n                        item.image.startsWith('/')\n                          ? `${process.env.NEXT_PUBLIC_SERVER_URL}${item.image}`\n                          : item.image\n                      }\n                    />\n                  </Column>\n                  <Column className='align-top'>\n                    {item.name} x {item.qty}\n                  </Column>\n                  <Column align='right' className='align-top'>\n                    {formatCurrency(item.price)}\n                  </Column>\n                </Row>\n              ))}\n              {[\n                { name: 'Items', price: order.itemsPrice },\n                { name: 'Tax', price: order.taxPrice },\n                { name: 'Shipping', price: order.shippingPrice },\n                { name: 'Total', price: order.totalPrice },\n              ].map(({ name, price }) => (\n                <Row key={name} className='py-1'>\n                  <Column align='right'>{name}: </Column>\n                  <Column align='right' width={70} className='align-top'>\n                    <Text className='m-0'>{formatCurrency(price)}</Text>\n                  </Column>\n                </Row>\n              ))}\n            </Section>\n          </Container>\n        </Body>\n      </Tailwind>\n    </Html>\n  );\n}\n"
  },
  {
    "path": "hooks/use-toast.ts",
    "content": "\"use client\"\n\n// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n  ToastActionElement,\n  ToastProps,\n} from \"@/components/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n  id: string\n  title?: React.ReactNode\n  description?: React.ReactNode\n  action?: ToastActionElement\n}\n\nconst actionTypes = {\n  ADD_TOAST: \"ADD_TOAST\",\n  UPDATE_TOAST: \"UPDATE_TOAST\",\n  DISMISS_TOAST: \"DISMISS_TOAST\",\n  REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n  count = (count + 1) % Number.MAX_SAFE_INTEGER\n  return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n  | {\n      type: ActionType[\"ADD_TOAST\"]\n      toast: ToasterToast\n    }\n  | {\n      type: ActionType[\"UPDATE_TOAST\"]\n      toast: Partial<ToasterToast>\n    }\n  | {\n      type: ActionType[\"DISMISS_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n  | {\n      type: ActionType[\"REMOVE_TOAST\"]\n      toastId?: ToasterToast[\"id\"]\n    }\n\ninterface State {\n  toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()\n\nconst addToRemoveQueue = (toastId: string) => {\n  if (toastTimeouts.has(toastId)) {\n    return\n  }\n\n  const timeout = setTimeout(() => {\n    toastTimeouts.delete(toastId)\n    dispatch({\n      type: \"REMOVE_TOAST\",\n      toastId: toastId,\n    })\n  }, TOAST_REMOVE_DELAY)\n\n  toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n  switch (action.type) {\n    case \"ADD_TOAST\":\n      return {\n        ...state,\n        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n      }\n\n    case \"UPDATE_TOAST\":\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === action.toast.id ? { ...t, ...action.toast } : t\n        ),\n      }\n\n    case \"DISMISS_TOAST\": {\n      const { toastId } = action\n\n      // ! Side effects ! - This could be extracted into a dismissToast() action,\n      // but I'll keep it here for simplicity\n      if (toastId) {\n        addToRemoveQueue(toastId)\n      } else {\n        state.toasts.forEach((toast) => {\n          addToRemoveQueue(toast.id)\n        })\n      }\n\n      return {\n        ...state,\n        toasts: state.toasts.map((t) =>\n          t.id === toastId || toastId === undefined\n            ? {\n                ...t,\n                open: false,\n              }\n            : t\n        ),\n      }\n    }\n    case \"REMOVE_TOAST\":\n      if (action.toastId === undefined) {\n        return {\n          ...state,\n          toasts: [],\n        }\n      }\n      return {\n        ...state,\n        toasts: state.toasts.filter((t) => t.id !== action.toastId),\n      }\n  }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n  memoryState = reducer(memoryState, action)\n  listeners.forEach((listener) => {\n    listener(memoryState)\n  })\n}\n\ntype Toast = Omit<ToasterToast, \"id\">\n\nfunction toast({ ...props }: Toast) {\n  const id = genId()\n\n  const update = (props: ToasterToast) =>\n    dispatch({\n      type: \"UPDATE_TOAST\",\n      toast: { ...props, id },\n    })\n  const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n  dispatch({\n    type: \"ADD_TOAST\",\n    toast: {\n      ...props,\n      id,\n      open: true,\n      onOpenChange: (open) => {\n        if (!open) dismiss()\n      },\n    },\n  })\n\n  return {\n    id: id,\n    dismiss,\n    update,\n  }\n}\n\nfunction useToast() {\n  const [state, setState] = React.useState<State>(memoryState)\n\n  React.useEffect(() => {\n    listeners.push(setState)\n    return () => {\n      const index = listeners.indexOf(setState)\n      if (index > -1) {\n        listeners.splice(index, 1)\n      }\n    }\n  }, [state])\n\n  return {\n    ...state,\n    toast,\n    dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n  }\n}\n\nexport { useToast, toast }\n"
  },
  {
    "path": "jest.config.ts",
    "content": "/**\n * For a detailed explanation regarding each configuration property, visit:\n * https://jestjs.io/docs/configuration\n */\n\nimport type { Config } from 'jest';\n\nconst config: Config = {\n  // All imported modules in your tests should be mocked automatically\n  // automock: false,\n\n  // Stop running tests after `n` failures\n  // bail: 0,\n\n  // The directory where Jest should store its cached dependency information\n  // cacheDirectory: \"/private/var/folders/39/tkk6884x1ydgmbvfshhs67vr0000gn/T/jest_dx\",\n\n  // Automatically clear mock calls, instances, contexts and results before every test\n  clearMocks: true,\n\n  // Indicates whether the coverage information should be collected while executing the test\n  // collectCoverage: false,\n\n  // An array of glob patterns indicating a set of files for which coverage information should be collected\n  // collectCoverageFrom: undefined,\n\n  // The directory where Jest should output its coverage files\n  // coverageDirectory: undefined,\n\n  // An array of regexp pattern strings used to skip coverage collection\n  // coveragePathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // Indicates which provider should be used to instrument code for coverage\n  coverageProvider: 'v8',\n\n  // A list of reporter names that Jest uses when writing coverage reports\n  // coverageReporters: [\n  //   \"json\",\n  //   \"text\",\n  //   \"lcov\",\n  //   \"clover\"\n  // ],\n\n  // An object that configures minimum threshold enforcement for coverage results\n  // coverageThreshold: undefined,\n\n  // A path to a custom dependency extractor\n  // dependencyExtractor: undefined,\n\n  // Make calling deprecated APIs throw helpful error messages\n  // errorOnDeprecated: false,\n\n  // The default configuration for fake timers\n  // fakeTimers: {\n  //   \"enableGlobally\": false\n  // },\n\n  // Force coverage collection from ignored files using an array of glob patterns\n  // forceCoverageMatch: [],\n\n  // A path to a module which exports an async function that is triggered once before all test suites\n  // globalSetup: undefined,\n\n  // A path to a module which exports an async function that is triggered once after all test suites\n  // globalTeardown: undefined,\n\n  // A set of global variables that need to be available in all test environments\n  // globals: {},\n\n  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.\n  // maxWorkers: \"50%\",\n\n  // An array of directory names to be searched recursively up from the requiring module's location\n  // moduleDirectories: [\n  //   \"node_modules\"\n  // ],\n\n  // An array of file extensions your modules use\n  // moduleFileExtensions: [\n  //   \"js\",\n  //   \"mjs\",\n  //   \"cjs\",\n  //   \"jsx\",\n  //   \"ts\",\n  //   \"tsx\",\n  //   \"json\",\n  //   \"node\"\n  // ],\n\n  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module\n  // moduleNameMapper: {},\n\n  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader\n  // modulePathIgnorePatterns: [],\n\n  // Activates notifications for test results\n  // notify: false,\n\n  // An enum that specifies notification mode. Requires { notify: true }\n  // notifyMode: \"failure-change\",\n\n  // A preset that is used as a base for Jest's configuration\n  preset: 'ts-jest',\n\n  // Run tests from one or more projects\n  // projects: undefined,\n\n  // Use this configuration option to add custom reporters to Jest\n  // reporters: undefined,\n\n  // Automatically reset mock state before every test\n  // resetMocks: false,\n\n  // Reset the module registry before running each individual test\n  // resetModules: false,\n\n  // A path to a custom resolver\n  // resolver: undefined,\n\n  // Automatically restore mock state and implementation before every test\n  // restoreMocks: false,\n\n  // The root directory that Jest should scan for tests and modules within\n  // rootDir: undefined,\n\n  // A list of paths to directories that Jest should use to search for files in\n  // roots: [\n  //   \"<rootDir>\"\n  // ],\n\n  // Allows you to use a custom runner instead of Jest's default test runner\n  // runner: \"jest-runner\",\n\n  // The paths to modules that run some code to configure or set up the testing environment before each test\n  setupFiles: ['<rootDir>/jest.setup.ts'],\n\n  // A list of paths to modules that run some code to configure or set up the testing framework before each test\n  // setupFilesAfterEnv: [],\n\n  // The number of seconds after which a test is considered as slow and reported as such in the results.\n  // slowTestThreshold: 5,\n\n  // A list of paths to snapshot serializer modules Jest should use for snapshot testing\n  // snapshotSerializers: [],\n\n  // The test environment that will be used for testing\n  // testEnvironment: \"jest-environment-node\",\n\n  // Options that will be passed to the testEnvironment\n  // testEnvironmentOptions: {},\n\n  // Adds a location field to test results\n  // testLocationInResults: false,\n\n  // The glob patterns Jest uses to detect test files\n  // testMatch: [\n  //   \"**/__tests__/**/*.[jt]s?(x)\",\n  //   \"**/?(*.)+(spec|test).[tj]s?(x)\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped\n  // testPathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // The regexp pattern or array of patterns that Jest uses to detect test files\n  // testRegex: [],\n\n  // This option allows the use of a custom results processor\n  // testResultsProcessor: undefined,\n\n  // This option allows use of a custom test runner\n  // testRunner: \"jest-circus/runner\",\n\n  // A map from regular expressions to paths to transformers\n  // transform: undefined,\n\n  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation\n  // transformIgnorePatterns: [\n  //   \"/node_modules/\",\n  //   \"\\\\.pnp\\\\.[^\\\\/]+$\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them\n  // unmockedModulePathPatterns: undefined,\n\n  // Indicates whether each individual test should be reported during the run\n  // verbose: undefined,\n\n  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode\n  // watchPathIgnorePatterns: [],\n\n  // Whether to use watchman for file crawling\n  // watchman: true,\n};\n\nexport default config;\n"
  },
  {
    "path": "jest.setup.ts",
    "content": "require('dotenv').config();\n"
  },
  {
    "path": "lib/actions/cart.actions.ts",
    "content": "'use server';\n\nimport { cookies } from 'next/headers';\nimport { CartItem } from '@/types';\nimport { convertToPlainObject, formatError, round2 } from '../utils';\nimport { auth } from '@/auth';\nimport { prisma } from '@/db/prisma';\nimport { cartItemSchema, insertCartSchema } from '../validators';\nimport { revalidatePath } from 'next/cache';\nimport { Prisma } from '@prisma/client';\n\n// Calculate cart prices\nconst calcPrice = (items: CartItem[]) => {\n  const itemsPrice = round2(\n      items.reduce((acc, item) => acc + Number(item.price) * item.qty, 0)\n    ),\n    shippingPrice = round2(itemsPrice > 100 ? 0 : 10),\n    taxPrice = round2(0.15 * itemsPrice),\n    totalPrice = round2(itemsPrice + taxPrice + shippingPrice);\n\n  return {\n    itemsPrice: itemsPrice.toFixed(2),\n    shippingPrice: shippingPrice.toFixed(2),\n    taxPrice: taxPrice.toFixed(2),\n    totalPrice: totalPrice.toFixed(2),\n  };\n};\n\nexport async function addItemToCart(data: CartItem) {\n  try {\n    // Check for cart cookie\n    const sessionCartId = (await cookies()).get('sessionCartId')?.value;\n    if (!sessionCartId) throw new Error('Cart session not found');\n\n    // Get session and user ID\n    const session = await auth();\n    const userId = session?.user?.id ? (session.user.id as string) : undefined;\n\n    // Get cart\n    const cart = await getMyCart();\n\n    // Parse and validate item\n    const item = cartItemSchema.parse(data);\n\n    // Find product in database\n    const product = await prisma.product.findFirst({\n      where: { id: item.productId },\n    });\n    if (!product) throw new Error('Product not found');\n\n    if (!cart) {\n      // Create new cart object\n      const newCart = insertCartSchema.parse({\n        userId: userId,\n        items: [item],\n        sessionCartId: sessionCartId,\n        ...calcPrice([item]),\n      });\n\n      // Add to database\n      await prisma.cart.create({\n        data: newCart,\n      });\n\n      // Revalidate product page\n      revalidatePath(`/product/${product.slug}`);\n\n      return {\n        success: true,\n        message: `${product.name} added to cart`,\n      };\n    } else {\n      // Check if item is already in cart\n      const existItem = (cart.items as CartItem[]).find(\n        (x) => x.productId === item.productId\n      );\n\n      if (existItem) {\n        // Check stock\n        if (product.stock < existItem.qty + 1) {\n          throw new Error('Not enough stock');\n        }\n\n        // Increase the quantity\n        (cart.items as CartItem[]).find(\n          (x) => x.productId === item.productId\n        )!.qty = existItem.qty + 1;\n      } else {\n        // If item does not exist in cart\n        // Check stock\n        if (product.stock < 1) throw new Error('Not enough stock');\n\n        // Add item to the cart.items\n        cart.items.push(item);\n      }\n\n      // Save to database\n      await prisma.cart.update({\n        where: { id: cart.id },\n        data: {\n          items: cart.items as Prisma.CartUpdateitemsInput[],\n          ...calcPrice(cart.items as CartItem[]),\n        },\n      });\n\n      revalidatePath(`/product/${product.slug}`);\n\n      return {\n        success: true,\n        message: `${product.name} ${\n          existItem ? 'updated in' : 'added to'\n        } cart`,\n      };\n    }\n  } catch (error) {\n    return {\n      success: false,\n      message: formatError(error),\n    };\n  }\n}\n\nexport async function getMyCart() {\n  // Check for cart cookie\n  const sessionCartId = (await cookies()).get('sessionCartId')?.value;\n  if (!sessionCartId) throw new Error('Cart session not found');\n\n  // Get session and user ID\n  const session = await auth();\n  const userId = session?.user?.id ? (session.user.id as string) : undefined;\n\n  // Get user cart from database\n  const cart = await prisma.cart.findFirst({\n    where: userId ? { userId: userId } : { sessionCartId: sessionCartId },\n  });\n\n  if (!cart) return undefined;\n\n  // Convert decimals and return\n  return convertToPlainObject({\n    ...cart,\n    items: cart.items as CartItem[],\n    itemsPrice: cart.itemsPrice.toString(),\n    totalPrice: cart.totalPrice.toString(),\n    shippingPrice: cart.shippingPrice.toString(),\n    taxPrice: cart.taxPrice.toString(),\n  });\n}\n\nexport async function removeItemFromCart(productId: string) {\n  try {\n    // Check for cart cookie\n    const sessionCartId = (await cookies()).get('sessionCartId')?.value;\n    if (!sessionCartId) throw new Error('Cart session not found');\n\n    // Get Product\n    const product = await prisma.product.findFirst({\n      where: { id: productId },\n    });\n    if (!product) throw new Error('Product not found');\n\n    // Get user cart\n    const cart = await getMyCart();\n    if (!cart) throw new Error('Cart not found');\n\n    // Check for item\n    const exist = (cart.items as CartItem[]).find(\n      (x) => x.productId === productId\n    );\n    if (!exist) throw new Error('Item not found');\n\n    // Check if only one in qty\n    if (exist.qty === 1) {\n      // Remove from cart\n      cart.items = (cart.items as CartItem[]).filter(\n        (x) => x.productId !== exist.productId\n      );\n    } else {\n      // Decrease qty\n      (cart.items as CartItem[]).find((x) => x.productId === productId)!.qty =\n        exist.qty - 1;\n    }\n\n    // Update cart in database\n    await prisma.cart.update({\n      where: { id: cart.id },\n      data: {\n        items: cart.items as Prisma.CartUpdateitemsInput[],\n        ...calcPrice(cart.items as CartItem[]),\n      },\n    });\n\n    revalidatePath(`/product/${product.slug}`);\n\n    return {\n      success: true,\n      message: `${product.name} was removed from cart`,\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n"
  },
  {
    "path": "lib/actions/order.actions.ts",
    "content": "'use server';\n\nimport { isRedirectError } from 'next/dist/client/components/redirect';\nimport { convertToPlainObject, formatError } from '../utils';\nimport { auth } from '@/auth';\nimport { getMyCart } from './cart.actions';\nimport { getUserById } from './user.actions';\nimport { insertOrderSchema } from '../validators';\nimport { prisma } from '@/db/prisma';\nimport { CartItem, PaymentResult, ShippingAddress } from '@/types';\nimport { paypal } from '../paypal';\nimport { revalidatePath } from 'next/cache';\nimport { PAGE_SIZE } from '../constants';\nimport { Prisma } from '@prisma/client';\nimport { sendPurchaseReceipt } from '@/email';\n\n// Create order and create the order items\nexport async function createOrder() {\n  try {\n    const session = await auth();\n    if (!session) throw new Error('User is not authenticated');\n\n    const cart = await getMyCart();\n    const userId = session?.user?.id;\n    if (!userId) throw new Error('User not found');\n\n    const user = await getUserById(userId);\n\n    if (!cart || cart.items.length === 0) {\n      return {\n        success: false,\n        message: 'Your cart is empty',\n        redirectTo: '/cart',\n      };\n    }\n\n    if (!user.address) {\n      return {\n        success: false,\n        message: 'No shipping address',\n        redirectTo: '/shipping-address',\n      };\n    }\n\n    if (!user.paymentMethod) {\n      return {\n        success: false,\n        message: 'No payment method',\n        redirectTo: '/payment-method',\n      };\n    }\n\n    // Create order object\n    const order = insertOrderSchema.parse({\n      userId: user.id,\n      shippingAddress: user.address,\n      paymentMethod: user.paymentMethod,\n      itemsPrice: cart.itemsPrice,\n      shippingPrice: cart.shippingPrice,\n      taxPrice: cart.taxPrice,\n      totalPrice: cart.totalPrice,\n    });\n\n    // Create a transaction to create order and order items in database\n    const insertedOrderId = await prisma.$transaction(async (tx) => {\n      // Create order\n      const insertedOrder = await tx.order.create({ data: order });\n      // Create order items from the cart items\n      for (const item of cart.items as CartItem[]) {\n        await tx.orderItem.create({\n          data: {\n            ...item,\n            price: item.price,\n            orderId: insertedOrder.id,\n          },\n        });\n      }\n      // Clear cart\n      await tx.cart.update({\n        where: { id: cart.id },\n        data: {\n          items: [],\n          totalPrice: 0,\n          taxPrice: 0,\n          shippingPrice: 0,\n          itemsPrice: 0,\n        },\n      });\n\n      return insertedOrder.id;\n    });\n\n    if (!insertedOrderId) throw new Error('Order not created');\n\n    return {\n      success: true,\n      message: 'Order created',\n      redirectTo: `/order/${insertedOrderId}`,\n    };\n  } catch (error) {\n    if (isRedirectError(error)) throw error;\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Get order by id\nexport async function getOrderById(orderId: string) {\n  const data = await prisma.order.findFirst({\n    where: {\n      id: orderId,\n    },\n    include: {\n      orderitems: true,\n      user: { select: { name: true, email: true } },\n    },\n  });\n\n  return convertToPlainObject(data);\n}\n\n// Create new paypal order\nexport async function createPayPalOrder(orderId: string) {\n  try {\n    // Get order from database\n    const order = await prisma.order.findFirst({\n      where: {\n        id: orderId,\n      },\n    });\n\n    if (order) {\n      // Create paypal order\n      const paypalOrder = await paypal.createOrder(Number(order.totalPrice));\n\n      // Update order with paypal order id\n      await prisma.order.update({\n        where: { id: orderId },\n        data: {\n          paymentResult: {\n            id: paypalOrder.id,\n            email_address: '',\n            status: '',\n            pricePaid: 0,\n          },\n        },\n      });\n\n      return {\n        success: true,\n        message: 'Item order created successfully',\n        data: paypalOrder.id,\n      };\n    } else {\n      throw new Error('Order not found');\n    }\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Approve paypal order and update order to paid\nexport async function approvePayPalOrder(\n  orderId: string,\n  data: { orderID: string }\n) {\n  try {\n    // Get order from database\n    const order = await prisma.order.findFirst({\n      where: {\n        id: orderId,\n      },\n    });\n\n    if (!order) throw new Error('Order not found');\n\n    const captureData = await paypal.capturePayment(data.orderID);\n\n    if (\n      !captureData ||\n      captureData.id !== (order.paymentResult as PaymentResult)?.id ||\n      captureData.status !== 'COMPLETED'\n    ) {\n      throw new Error('Error in PayPal payment');\n    }\n\n    // Update order to paid\n    await updateOrderToPaid({\n      orderId,\n      paymentResult: {\n        id: captureData.id,\n        status: captureData.status,\n        email_address: captureData.payer.email_address,\n        pricePaid:\n          captureData.purchase_units[0]?.payments?.captures[0]?.amount?.value,\n      },\n    });\n\n    revalidatePath(`/order/${orderId}`);\n\n    return {\n      success: true,\n      message: 'Your order has been paid',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Update order to paid\nexport async function updateOrderToPaid({\n  orderId,\n  paymentResult,\n}: {\n  orderId: string;\n  paymentResult?: PaymentResult;\n}) {\n  // Get order from database\n  const order = await prisma.order.findFirst({\n    where: {\n      id: orderId,\n    },\n    include: {\n      orderitems: true,\n    },\n  });\n\n  if (!order) throw new Error('Order not found');\n\n  if (order.isPaid) throw new Error('Order is already paid');\n\n  // Transaction to update order and account for product stock\n  await prisma.$transaction(async (tx) => {\n    // Iterate over products and update stock\n    for (const item of order.orderitems) {\n      await tx.product.update({\n        where: { id: item.productId },\n        data: { stock: { increment: -item.qty } },\n      });\n    }\n\n    // Set the order to paid\n    await tx.order.update({\n      where: { id: orderId },\n      data: {\n        isPaid: true,\n        paidAt: new Date(),\n        paymentResult,\n      },\n    });\n  });\n\n  // Get updated order after transaction\n  const updatedOrder = await prisma.order.findFirst({\n    where: { id: orderId },\n    include: {\n      orderitems: true,\n      user: { select: { name: true, email: true } },\n    },\n  });\n\n  if (!updatedOrder) throw new Error('Order not found');\n\n  sendPurchaseReceipt({\n    order: {\n      ...updatedOrder,\n      shippingAddress: updatedOrder.shippingAddress as ShippingAddress,\n      paymentResult: updatedOrder.paymentResult as PaymentResult,\n    },\n  });\n}\n\n// Get user's orders\nexport async function getMyOrders({\n  limit = PAGE_SIZE,\n  page,\n}: {\n  limit?: number;\n  page: number;\n}) {\n  const session = await auth();\n  if (!session) throw new Error('User is not authorized');\n\n  const data = await prisma.order.findMany({\n    where: { userId: session?.user?.id },\n    orderBy: { createdAt: 'desc' },\n    take: limit,\n    skip: (page - 1) * limit,\n  });\n\n  const dataCount = await prisma.order.count({\n    where: { userId: session?.user?.id },\n  });\n\n  return {\n    data,\n    totalPages: Math.ceil(dataCount / limit),\n  };\n}\n\ntype SalesDataType = {\n  month: string;\n  totalSales: number;\n}[];\n\n// Get sales data and order summary\nexport async function getOrderSummary() {\n  // Get counts for each resource\n  const ordersCount = await prisma.order.count();\n  const productsCount = await prisma.product.count();\n  const usersCount = await prisma.user.count();\n\n  // Calculate the total sales\n  const totalSales = await prisma.order.aggregate({\n    _sum: { totalPrice: true },\n  });\n\n  // Get monthly sales\n  const salesDataRaw = await prisma.$queryRaw<\n    Array<{ month: string; totalSales: Prisma.Decimal }>\n  >`SELECT to_char(\"createdAt\", 'MM/YY') as \"month\", sum(\"totalPrice\") as \"totalSales\" FROM \"Order\" GROUP BY to_char(\"createdAt\", 'MM/YY')`;\n\n  const salesData: SalesDataType = salesDataRaw.map((entry) => ({\n    month: entry.month,\n    totalSales: Number(entry.totalSales),\n  }));\n\n  // Get latest sales\n  const latestSales = await prisma.order.findMany({\n    orderBy: { createdAt: 'desc' },\n    include: {\n      user: { select: { name: true } },\n    },\n    take: 6,\n  });\n\n  return {\n    ordersCount,\n    productsCount,\n    usersCount,\n    totalSales,\n    latestSales,\n    salesData,\n  };\n}\n\n// Get all orders\nexport async function getAllOrders({\n  limit = PAGE_SIZE,\n  page,\n  query,\n}: {\n  limit?: number;\n  page: number;\n  query: string;\n}) {\n  const queryFilter: Prisma.OrderWhereInput =\n    query && query !== 'all'\n      ? {\n          user: {\n            name: {\n              contains: query,\n              mode: 'insensitive',\n            } as Prisma.StringFilter,\n          },\n        }\n      : {};\n\n  const data = await prisma.order.findMany({\n    where: {\n      ...queryFilter,\n    },\n    orderBy: { createdAt: 'desc' },\n    take: limit,\n    skip: (page - 1) * limit,\n    include: { user: { select: { name: true } } },\n  });\n\n  const dataCount = await prisma.order.count();\n\n  return {\n    data,\n    totalPages: Math.ceil(dataCount / limit),\n  };\n}\n\n// Delete an order\nexport async function deleteOrder(id: string) {\n  try {\n    await prisma.order.delete({ where: { id } });\n\n    revalidatePath('/admin/orders');\n\n    return {\n      success: true,\n      message: 'Order deleted successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Update COD order to paid\nexport async function updateOrderToPaidCOD(orderId: string) {\n  try {\n    await updateOrderToPaid({ orderId });\n\n    revalidatePath(`/order/${orderId}`);\n\n    return { success: true, message: 'Order marked as paid' };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Update COD order to delivered\nexport async function deliverOrder(orderId: string) {\n  try {\n    const order = await prisma.order.findFirst({\n      where: {\n        id: orderId,\n      },\n    });\n\n    if (!order) throw new Error('Order not found');\n    if (!order.isPaid) throw new Error('Order is not paid');\n\n    await prisma.order.update({\n      where: { id: orderId },\n      data: {\n        isDelivered: true,\n        deliveredAt: new Date(),\n      },\n    });\n\n    revalidatePath(`/order/${orderId}`);\n\n    return {\n      success: true,\n      message: 'Order has been marked delivered',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n"
  },
  {
    "path": "lib/actions/product.actions.ts",
    "content": "'use server';\nimport { prisma } from '@/db/prisma';\nimport { convertToPlainObject, formatError } from '../utils';\nimport { LATEST_PRODUCTS_LIMIT, PAGE_SIZE } from '../constants';\nimport { revalidatePath } from 'next/cache';\nimport { insertProductSchema, updateProductSchema } from '../validators';\nimport { z } from 'zod';\nimport { Prisma } from '@prisma/client';\n\n// Get latest products\nexport async function getLatestProducts() {\n  const data = await prisma.product.findMany({\n    take: LATEST_PRODUCTS_LIMIT,\n    orderBy: { createdAt: 'desc' },\n  });\n\n  return convertToPlainObject(data);\n}\n\n// Get single product by it's slug\nexport async function getProductBySlug(slug: string) {\n  return await prisma.product.findFirst({\n    where: { slug: slug },\n  });\n}\n\n// Get single product by it's ID\nexport async function getProductById(productId: string) {\n  const data = await prisma.product.findFirst({\n    where: { id: productId },\n  });\n\n  return convertToPlainObject(data);\n}\n\n// Get all products\nexport async function getAllProducts({\n  query,\n  limit = PAGE_SIZE,\n  page,\n  category,\n  price,\n  rating,\n  sort,\n}: {\n  query: string;\n  limit?: number;\n  page: number;\n  category?: string;\n  price?: string;\n  rating?: string;\n  sort?: string;\n}) {\n  // Query filter\n  const queryFilter: Prisma.ProductWhereInput =\n    query && query !== 'all'\n      ? {\n          name: {\n            contains: query,\n            mode: 'insensitive',\n          } as Prisma.StringFilter,\n        }\n      : {};\n\n  // Category filter\n  const categoryFilter = category && category !== 'all' ? { category } : {};\n\n  // Price filter\n  const priceFilter: Prisma.ProductWhereInput =\n    price && price !== 'all'\n      ? {\n          price: {\n            gte: Number(price.split('-')[0]),\n            lte: Number(price.split('-')[1]),\n          },\n        }\n      : {};\n\n  // Rating filter\n  const ratingFilter =\n    rating && rating !== 'all'\n      ? {\n          rating: {\n            gte: Number(rating),\n          },\n        }\n      : {};\n\n  const data = await prisma.product.findMany({\n    where: {\n      ...queryFilter,\n      ...categoryFilter,\n      ...priceFilter,\n      ...ratingFilter,\n    },\n    orderBy:\n      sort === 'lowest'\n        ? { price: 'asc' }\n        : sort === 'highest'\n        ? { price: 'desc' }\n        : sort === 'rating'\n        ? { rating: 'desc' }\n        : { createdAt: 'desc' },\n    skip: (page - 1) * limit,\n    take: limit,\n  });\n\n  const dataCount = await prisma.product.count();\n\n  return {\n    data,\n    totalPages: Math.ceil(dataCount / limit),\n  };\n}\n\n// Delete a product\nexport async function deleteProduct(id: string) {\n  try {\n    const productExists = await prisma.product.findFirst({\n      where: { id },\n    });\n\n    if (!productExists) throw new Error('Product not found');\n\n    await prisma.product.delete({ where: { id } });\n\n    revalidatePath('/admin/products');\n\n    return {\n      success: true,\n      message: 'Product deleted successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Create a product\nexport async function createProduct(data: z.infer<typeof insertProductSchema>) {\n  try {\n    const product = insertProductSchema.parse(data);\n    await prisma.product.create({ data: product });\n\n    revalidatePath('/admin/products');\n\n    return {\n      success: true,\n      message: 'Product created successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Update a product\nexport async function updateProduct(data: z.infer<typeof updateProductSchema>) {\n  try {\n    const product = updateProductSchema.parse(data);\n    const productExists = await prisma.product.findFirst({\n      where: { id: product.id },\n    });\n\n    if (!productExists) throw new Error('Product not found');\n\n    await prisma.product.update({\n      where: { id: product.id },\n      data: product,\n    });\n\n    revalidatePath('/admin/products');\n\n    return {\n      success: true,\n      message: 'Product updated successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Get all categories\nexport async function getAllCategories() {\n  const data = await prisma.product.groupBy({\n    by: ['category'],\n    _count: true,\n  });\n\n  return data;\n}\n\n// Get featured products\nexport async function getFeaturedProducts() {\n  const data = await prisma.product.findMany({\n    where: { isFeatured: true },\n    orderBy: { createdAt: 'desc' },\n    take: 4,\n  });\n\n  return convertToPlainObject(data);\n}\n"
  },
  {
    "path": "lib/actions/review.actions.ts",
    "content": "'use server';\n\nimport { z } from 'zod';\nimport { insertReviewSchema } from '../validators';\nimport { formatError } from '../utils';\nimport { auth } from '@/auth';\nimport { prisma } from '@/db/prisma';\nimport { revalidatePath } from 'next/cache';\n\n// Create & Update Reviews\nexport async function createUpdateReview(\n  data: z.infer<typeof insertReviewSchema>\n) {\n  try {\n    const session = await auth();\n    if (!session) throw new Error('User is not authenticated');\n\n    // Validate and store the review\n    const review = insertReviewSchema.parse({\n      ...data,\n      userId: session?.user?.id,\n    });\n\n    // Get product that is being reviewed\n    const product = await prisma.product.findFirst({\n      where: { id: review.productId },\n    });\n\n    if (!product) throw new Error('Product not found');\n\n    // Check if user already reviewed\n    const reviewExists = await prisma.review.findFirst({\n      where: {\n        productId: review.productId,\n        userId: review.userId,\n      },\n    });\n\n    await prisma.$transaction(async (tx) => {\n      if (reviewExists) {\n        // Update review\n        await tx.review.update({\n          where: { id: reviewExists.id },\n          data: {\n            title: review.title,\n            description: review.description,\n            rating: review.rating,\n          },\n        });\n      } else {\n        // Create review\n        await tx.review.create({ data: review });\n      }\n\n      // Get avg rating\n      const averageRating = await tx.review.aggregate({\n        _avg: { rating: true },\n        where: { productId: review.productId },\n      });\n\n      // Get number of reviews\n      const numReviews = await tx.review.count({\n        where: { productId: review.productId },\n      });\n\n      // Update the rating and numReviews in product table\n      await tx.product.update({\n        where: { id: review.productId },\n        data: {\n          rating: averageRating._avg.rating || 0,\n          numReviews,\n        },\n      });\n    });\n\n    revalidatePath(`/product/${product.slug}`);\n\n    return {\n      success: true,\n      message: 'Review Updated Successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Get all reviews for a product\nexport async function getReviews({ productId }: { productId: string }) {\n  const data = await prisma.review.findMany({\n    where: {\n      productId: productId,\n    },\n    include: {\n      user: {\n        select: {\n          name: true,\n        },\n      },\n    },\n    orderBy: {\n      createdAt: 'desc',\n    },\n  });\n\n  return { data };\n}\n\n// Get a review written by the current user\nexport async function getReviewByProductId({\n  productId,\n}: {\n  productId: string;\n}) {\n  const session = await auth();\n\n  if (!session) throw new Error('User is not authenticated');\n\n  return await prisma.review.findFirst({\n    where: {\n      productId,\n      userId: session?.user?.id,\n    },\n  });\n}\n"
  },
  {
    "path": "lib/actions/user.actions.ts",
    "content": "'use server';\n\nimport {\n  shippingAddressSchema,\n  signInFormSchema,\n  signUpFormSchema,\n  paymentMethodSchema,\n  updateUserSchema,\n} from '../validators';\nimport { auth, signIn, signOut } from '@/auth';\nimport { isRedirectError } from 'next/dist/client/components/redirect-error';\nimport { hash } from '../encrypt';\nimport { prisma } from '@/db/prisma';\nimport { formatError } from '../utils';\nimport { ShippingAddress } from '@/types';\nimport { z } from 'zod';\nimport { PAGE_SIZE } from '../constants';\nimport { revalidatePath } from 'next/cache';\nimport { Prisma } from '@prisma/client';\nimport { getMyCart } from './cart.actions';\n\n// Sign in the user with credentials\nexport async function signInWithCredentials(\n  prevState: unknown,\n  formData: FormData\n) {\n  try {\n    const user = signInFormSchema.parse({\n      email: formData.get('email'),\n      password: formData.get('password'),\n    });\n\n    await signIn('credentials', user);\n\n    return { success: true, message: 'Signed in successfully' };\n  } catch (error) {\n    if (isRedirectError(error)) {\n      throw error;\n    }\n    return { success: false, message: 'Invalid email or password' };\n  }\n}\n\n// Sign user out\nexport async function signOutUser() {\n  // get current users cart and delete it so it does not persist to next user\n  const currentCart = await getMyCart();\n\n  if (currentCart?.id) {\n    await prisma.cart.delete({ where: { id: currentCart.id } });\n  } else {\n    console.warn('No cart found for deletion.');\n  }\n  await signOut();\n}\n\n// Sign up user\nexport async function signUpUser(prevState: unknown, formData: FormData) {\n  try {\n    const user = signUpFormSchema.parse({\n      name: formData.get('name'),\n      email: formData.get('email'),\n      password: formData.get('password'),\n      confirmPassword: formData.get('confirmPassword'),\n    });\n\n    const plainPassword = user.password;\n\n    user.password = await hash(user.password);\n\n    await prisma.user.create({\n      data: {\n        name: user.name,\n        email: user.email,\n        password: user.password,\n      },\n    });\n\n    await signIn('credentials', {\n      email: user.email,\n      password: plainPassword,\n    });\n\n    return { success: true, message: 'User registered successfully' };\n  } catch (error) {\n    if (isRedirectError(error)) {\n      throw error;\n    }\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Get user by the ID\nexport async function getUserById(userId: string) {\n  const user = await prisma.user.findFirst({\n    where: { id: userId },\n  });\n  if (!user) throw new Error('User not found');\n  return user;\n}\n\n// Update the user's address\nexport async function updateUserAddress(data: ShippingAddress) {\n  try {\n    const session = await auth();\n\n    const currentUser = await prisma.user.findFirst({\n      where: { id: session?.user?.id },\n    });\n\n    if (!currentUser) throw new Error('User not found');\n\n    const address = shippingAddressSchema.parse(data);\n\n    await prisma.user.update({\n      where: { id: currentUser.id },\n      data: { address },\n    });\n\n    return {\n      success: true,\n      message: 'User updated successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Update user's payment method\nexport async function updateUserPaymentMethod(\n  data: z.infer<typeof paymentMethodSchema>\n) {\n  try {\n    const session = await auth();\n    const currentUser = await prisma.user.findFirst({\n      where: { id: session?.user?.id },\n    });\n\n    if (!currentUser) throw new Error('User not found');\n\n    const paymentMethod = paymentMethodSchema.parse(data);\n\n    await prisma.user.update({\n      where: { id: currentUser.id },\n      data: { paymentMethod: paymentMethod.type },\n    });\n\n    return {\n      success: true,\n      message: 'User updated successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Update the user profile\nexport async function updateProfile(user: { name: string; email: string }) {\n  try {\n    const session = await auth();\n\n    const currentUser = await prisma.user.findFirst({\n      where: {\n        id: session?.user?.id,\n      },\n    });\n\n    if (!currentUser) throw new Error('User not found');\n\n    await prisma.user.update({\n      where: {\n        id: currentUser.id,\n      },\n      data: {\n        name: user.name,\n      },\n    });\n\n    return {\n      success: true,\n      message: 'User updated successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n\n// Get all the users\nexport async function getAllUsers({\n  limit = PAGE_SIZE,\n  page,\n  query,\n}: {\n  limit?: number;\n  page: number;\n  query: string;\n}) {\n  const queryFilter: Prisma.UserWhereInput =\n    query && query !== 'all'\n      ? {\n          name: {\n            contains: query,\n            mode: 'insensitive',\n          } as Prisma.StringFilter,\n        }\n      : {};\n\n  const data = await prisma.user.findMany({\n    where: {\n      ...queryFilter,\n    },\n    orderBy: { createdAt: 'desc' },\n    take: limit,\n    skip: (page - 1) * limit,\n  });\n\n  const dataCount = await prisma.user.count();\n\n  return {\n    data,\n    totalPages: Math.ceil(dataCount / limit),\n  };\n}\n\n// Delete a user\nexport async function deleteUser(id: string) {\n  try {\n    await prisma.user.delete({ where: { id } });\n\n    revalidatePath('/admin/users');\n\n    return {\n      success: true,\n      message: 'User deleted successfully',\n    };\n  } catch (error) {\n    return {\n      success: false,\n      message: formatError(error),\n    };\n  }\n}\n\n// Update a user\nexport async function updateUser(user: z.infer<typeof updateUserSchema>) {\n  try {\n    await prisma.user.update({\n      where: { id: user.id },\n      data: {\n        name: user.name,\n        role: user.role,\n      },\n    });\n\n    revalidatePath('/admin/users');\n\n    return {\n      success: true,\n      message: 'User updated successfully',\n    };\n  } catch (error) {\n    return { success: false, message: formatError(error) };\n  }\n}\n"
  },
  {
    "path": "lib/auth-guard.ts",
    "content": "import { auth } from '@/auth'\nimport { redirect } from 'next/navigation'\n\nexport async function requireAdmin() {\n  const session = await auth()\n\n  if (session?.user?.role !== 'admin') {\n    redirect('/unauthorized')\n  }\n\n  return session\n}\n"
  },
  {
    "path": "lib/constants/index.ts",
    "content": "export const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME || 'Prostore';\nexport const APP_DESCRIPTION =\n  process.env.NEXT_PUBLIC_APP_DESCRIPTION ||\n  'A modern ecommerce store built with Next.js';\nexport const SERVER_URL =\n  process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000';\nexport const LATEST_PRODUCTS_LIMIT =\n  Number(process.env.LATEST_PRODUCTS_LIMIT) || 4;\n\nexport const signInDefaultValues = {\n  email: 'admin@example.com',\n  password: '123456',\n};\n\nexport const signUpDefaultValues = {\n  name: '',\n  email: '',\n  password: '',\n  confirmPassword: '',\n};\n\nexport const shippingAddressDefaultValues = {\n  fullName: '',\n  streetAddress: '',\n  city: '',\n  postalCode: '',\n  country: '',\n};\n\nexport const PAYMENT_METHODS = process.env.PAYMENT_METHODS\n  ? process.env.PAYMENT_METHODS.split(', ')\n  : ['PayPal', 'Stripe', 'CashOnDelivery'];\nexport const DEFAULT_PAYMENT_METHOD =\n  process.env.DEFAULT_PAYMENT_METHOD || 'PayPal';\n\nexport const PAGE_SIZE = Number(process.env.PAGE_SIZE) || 12;\n\nexport const productDefaultValues = {\n  name: '',\n  slug: '',\n  category: '',\n  images: [],\n  brand: '',\n  description: '',\n  price: '0',\n  stock: 0,\n  rating: '0',\n  numReviews: '0',\n  isFeatured: false,\n  banner: null,\n};\n\nexport const USER_ROLES = process.env.USER_ROLES\n  ? process.env.USER_ROLES.split(', ')\n  : ['admin', 'user'];\n\nexport const reviewFormDefaultValues = {\n  title: '',\n  comment: '',\n  rating: 0,\n};\n\nexport const SENDER_EMAIL = process.env.SENDER_EMAIL || 'onboarding@resend.dev';\n"
  },
  {
    "path": "lib/encrypt.ts",
    "content": "const encoder = new TextEncoder();\nconst key = new TextEncoder().encode(process.env.ENCRYPTION_KEY); // Retrieve key from env var\n\n// Hash function with key-based encryption\nexport const hash = async (plainPassword: string): Promise<string> => {\n  const passwordData = encoder.encode(plainPassword);\n\n  const cryptoKey = await crypto.subtle.importKey(\n    'raw',\n    key,\n    { name: 'HMAC', hash: { name: 'SHA-256' } },\n    false,\n    ['sign', 'verify']\n  );\n\n  const hashBuffer = await crypto.subtle.sign('HMAC', cryptoKey, passwordData);\n  return Array.from(new Uint8Array(hashBuffer))\n    .map((b) => b.toString(16).padStart(2, '0'))\n    .join('');\n};\n\n// Compare function using key from env var\nexport const compare = async (\n  plainPassword: string,\n  encryptedPassword: string\n): Promise<boolean> => {\n  const hashedPassword = await hash(plainPassword);\n  return hashedPassword === encryptedPassword;\n};\n// // Use Web Crypto API compatible with Edge Functions\n\n// const encoder = new TextEncoder();\n// const salt = crypto.getRandomValues(new Uint8Array(16)).join('');\n\n// // Hash function\n// export const hash = async (plainPassword: string): Promise<string> => {\n//   const passwordData = encoder.encode(plainPassword + salt);\n//   const hashBuffer = await crypto.subtle.digest('SHA-256', passwordData);\n//   return Array.from(new Uint8Array(hashBuffer))\n//     .map((b) => b.toString(16).padStart(2, '0'))\n//     .join('');\n// };\n\n// // Compare function\n// export const compare = async (\n//   plainPassword: string,\n//   encryptedPassword: string\n// ): Promise<boolean> => {\n//   const hashedPassword = await hash(plainPassword);\n//   return hashedPassword === encryptedPassword;\n// };\n"
  },
  {
    "path": "lib/paypal.ts",
    "content": "const base = process.env.PAYPAL_API_URL || 'https://api-m.sandbox.paypal.com';\n\nexport const paypal = {\n  createOrder: async function createOrder(price: number) {\n    const accessToken = await generateAccessToken();\n    const url = `${base}/v2/checkout/orders`;\n\n    const response = await fetch(url, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: `Bearer ${accessToken}`,\n      },\n      body: JSON.stringify({\n        intent: 'CAPTURE',\n        purchase_units: [\n          {\n            amount: {\n              currency_code: 'USD',\n              value: price,\n            },\n          },\n        ],\n      }),\n    });\n\n    return handleResponse(response);\n  },\n  capturePayment: async function capturePayment(orderId: string) {\n    const accessToken = await generateAccessToken();\n    const url = `${base}/v2/checkout/orders/${orderId}/capture`;\n\n    const response = await fetch(url, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        Authorization: `Bearer ${accessToken}`,\n      },\n    });\n    return handleResponse(response);\n  },\n};\n\n// Generate paypal access token\nasync function generateAccessToken() {\n  const { PAYPAL_CLIENT_ID, PAYPAL_APP_SECRET } = process.env;\n  const auth = Buffer.from(`${PAYPAL_CLIENT_ID}:${PAYPAL_APP_SECRET}`).toString(\n    'base64'\n  );\n\n  const response = await fetch(`${base}/v1/oauth2/token`, {\n    method: 'POST',\n    body: 'grant_type=client_credentials',\n    headers: {\n      Authorization: `Basic ${auth}`,\n      'Content-Type': 'application/x-www-form-urlencoded',\n    },\n  });\n\n  const jsonData = await handleResponse(response);\n  return jsonData.access_token;\n}\n\nasync function handleResponse(response: Response) {\n  if (response.ok) {\n    return response.json();\n  } else {\n    const errorMessage = await response.text();\n    throw new Error(errorMessage);\n  }\n}\n\nexport { generateAccessToken };\n"
  },
  {
    "path": "lib/uploadthing.ts",
    "content": "import {\n  generateUploadButton,\n  generateUploadDropzone,\n} from '@uploadthing/react';\n\nimport type { OurFileRouter } from '@/app/api/uploadthing/core';\n\nexport const UploadButton = generateUploadButton<OurFileRouter>();\nexport const UploadDropzone = generateUploadDropzone<OurFileRouter>();\n"
  },
  {
    "path": "lib/utils.ts",
    "content": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\nimport qs from 'query-string';\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\n// Convert prisma object into a regular JS object\nexport function convertToPlainObject<T>(value: T): T {\n  return JSON.parse(JSON.stringify(value));\n}\n\n// Format number with decimal places\nexport function formatNumberWithDecimal(num: number): string {\n  const [int, decimal] = num.toString().split('.');\n  return decimal ? `${int}.${decimal.padEnd(2, '0')}` : `${int}.00`;\n}\n\n// Format errors\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function formatError(error: any) {\n  if (error.name === 'ZodError') {\n    // Handle Zod error\n    const fieldErrors = Object.keys(error.errors).map(\n      (field) => error.errors[field].message\n    );\n\n    return fieldErrors.join('. ');\n  } else if (\n    error.name === 'PrismaClientKnownRequestError' &&\n    error.code === 'P2002'\n  ) {\n    // Handle Prisma error\n    const field = error.meta?.target ? error.meta.target[0] : 'Field';\n    return `${field.charAt(0).toUpperCase() + field.slice(1)} already exists`;\n  } else {\n    // Handle other errors\n    return typeof error.message === 'string'\n      ? error.message\n      : JSON.stringify(error.message);\n  }\n}\n\n// Round number to 2 decimal places\nexport function round2(value: number | string) {\n  if (typeof value === 'number') {\n    return Math.round((value + Number.EPSILON) * 100) / 100;\n  } else if (typeof value === 'string') {\n    return Math.round((Number(value) + Number.EPSILON) * 100) / 100;\n  } else {\n    throw new Error('Value is not a number or string');\n  }\n}\n\nconst CURRENCY_FORMATTER = new Intl.NumberFormat('en-US', {\n  currency: 'USD',\n  style: 'currency',\n  minimumFractionDigits: 2,\n});\n\n// Format currency using the formatter above\nexport function formatCurrency(amount: number | string | null) {\n  if (typeof amount === 'number') {\n    return CURRENCY_FORMATTER.format(amount);\n  } else if (typeof amount === 'string') {\n    return CURRENCY_FORMATTER.format(Number(amount));\n  } else {\n    return 'NaN';\n  }\n}\n\n// Format Number\nconst NUMBER_FORMATTER = new Intl.NumberFormat('en-US');\n\nexport function formatNumber(number: number) {\n  return NUMBER_FORMATTER.format(number);\n}\n\n// Shorten UUID\nexport function formatId(id: string) {\n  return `..${id.substring(id.length - 6)}`;\n}\n\n// Format date and times\nexport const formatDateTime = (dateString: Date) => {\n  const dateTimeOptions: Intl.DateTimeFormatOptions = {\n    month: 'short', // abbreviated month name (e.g., 'Oct')\n    year: 'numeric', // abbreviated month name (e.g., 'Oct')\n    day: 'numeric', // numeric day of the month (e.g., '25')\n    hour: 'numeric', // numeric hour (e.g., '8')\n    minute: 'numeric', // numeric minute (e.g., '30')\n    hour12: true, // use 12-hour clock (true) or 24-hour clock (false)\n  };\n  const dateOptions: Intl.DateTimeFormatOptions = {\n    weekday: 'short', // abbreviated weekday name (e.g., 'Mon')\n    month: 'short', // abbreviated month name (e.g., 'Oct')\n    year: 'numeric', // numeric year (e.g., '2023')\n    day: 'numeric', // numeric day of the month (e.g., '25')\n  };\n  const timeOptions: Intl.DateTimeFormatOptions = {\n    hour: 'numeric', // numeric hour (e.g., '8')\n    minute: 'numeric', // numeric minute (e.g., '30')\n    hour12: true, // use 12-hour clock (true) or 24-hour clock (false)\n  };\n  const formattedDateTime: string = new Date(dateString).toLocaleString(\n    'en-US',\n    dateTimeOptions\n  );\n  const formattedDate: string = new Date(dateString).toLocaleString(\n    'en-US',\n    dateOptions\n  );\n  const formattedTime: string = new Date(dateString).toLocaleString(\n    'en-US',\n    timeOptions\n  );\n  return {\n    dateTime: formattedDateTime,\n    dateOnly: formattedDate,\n    timeOnly: formattedTime,\n  };\n};\n\n// Form the pagination links\nexport function formUrlQuery({\n  params,\n  key,\n  value,\n}: {\n  params: string;\n  key: string;\n  value: string | null;\n}) {\n  const query = qs.parse(params);\n\n  query[key] = value;\n\n  return qs.stringifyUrl(\n    {\n      url: window.location.pathname,\n      query,\n    },\n    {\n      skipNull: true,\n    }\n  );\n}\n"
  },
  {
    "path": "lib/validators.ts",
    "content": "import { z } from 'zod';\nimport { formatNumberWithDecimal } from './utils';\nimport { PAYMENT_METHODS } from './constants';\n\nconst currency = z\n  .string()\n  .refine(\n    (value) => /^\\d+(\\.\\d{2})?$/.test(formatNumberWithDecimal(Number(value))),\n    'Price must have exactly two decimal places'\n  );\n\n// Schema for inserting products\nexport const insertProductSchema = z.object({\n  name: z.string().min(3, 'Name must be at least 3 characters'),\n  slug: z.string().min(3, 'Slug must be at least 3 characters'),\n  category: z.string().min(3, 'Category must be at least 3 characters'),\n  brand: z.string().min(3, 'Brand must be at least 3 characters'),\n  description: z.string().min(3, 'Description must be at least 3 characters'),\n  stock: z.coerce.number(),\n  images: z.array(z.string()).min(1, 'Product must have at least one image'),\n  isFeatured: z.boolean(),\n  banner: z.string().nullable(),\n  price: currency,\n});\n\n// Schema for updating products\nexport const updateProductSchema = insertProductSchema.extend({\n  id: z.string().min(1, 'Id is required'),\n});\n\n// Schema for signing users in\nexport const signInFormSchema = z.object({\n  email: z.string().email('Invalid email address'),\n  password: z.string().min(6, 'Password must be at least 6 characters'),\n});\n\n// Schema for signing up a user\nexport const signUpFormSchema = z\n  .object({\n    name: z.string().min(3, 'Name must be at least 3 characters'),\n    email: z.string().email('Invalid email address'),\n    password: z.string().min(6, 'Password must be at least 6 characters'),\n    confirmPassword: z\n      .string()\n      .min(6, 'Confirm password must be at least 6 characters'),\n  })\n  .refine((data) => data.password === data.confirmPassword, {\n    message: \"Passwords don't match\",\n    path: ['confirmPassword'],\n  });\n\n// Cart Schemas\nexport const cartItemSchema = z.object({\n  productId: z.string().min(1, 'Product is required'),\n  name: z.string().min(1, 'Name is required'),\n  slug: z.string().min(1, 'Slug is required'),\n  qty: z.number().int().nonnegative('Quantity must be a positive number'),\n  image: z.string().min(1, 'Image is required'),\n  price: currency,\n});\n\nexport const insertCartSchema = z.object({\n  items: z.array(cartItemSchema),\n  itemsPrice: currency,\n  totalPrice: currency,\n  shippingPrice: currency,\n  taxPrice: currency,\n  sessionCartId: z.string().min(1, 'Session cart id is required'),\n  userId: z.string().optional().nullable(),\n});\n\n// Schema for the shipping address\nexport const shippingAddressSchema = z.object({\n  fullName: z.string().min(3, 'Name must be at least 3 characters'),\n  streetAddress: z.string().min(3, 'Address must be at least 3 characters'),\n  city: z.string().min(3, 'City must be at least 3 characters'),\n  postalCode: z.string().min(3, 'Postal code must be at least 3 characters'),\n  country: z.string().min(3, 'Country must be at least 3 characters'),\n  lat: z.number().optional(),\n  lng: z.number().optional(),\n});\n\n// Schema for payment method\nexport const paymentMethodSchema = z\n  .object({\n    type: z.string().min(1, 'Payment method is required'),\n  })\n  .refine((data) => PAYMENT_METHODS.includes(data.type), {\n    path: ['type'],\n    message: 'Invalid payment method',\n  });\n\n// Schema for inserting order\nexport const insertOrderSchema = z.object({\n  userId: z.string().min(1, 'User is required'),\n  itemsPrice: currency,\n  shippingPrice: currency,\n  taxPrice: currency,\n  totalPrice: currency,\n  paymentMethod: z.string().refine((data) => PAYMENT_METHODS.includes(data), {\n    message: 'Invalid payment method',\n  }),\n  shippingAddress: shippingAddressSchema,\n});\n\n// Schema for inserting an order item\nexport const insertOrderItemSchema = z.object({\n  productId: z.string(),\n  slug: z.string(),\n  image: z.string(),\n  name: z.string(),\n  price: currency,\n  qty: z.number(),\n});\n\n// Schema for the PayPal paymentResult\nexport const paymentResultSchema = z.object({\n  id: z.string(),\n  status: z.string(),\n  email_address: z.string(),\n  pricePaid: z.string(),\n});\n\n// Schema for updating the user profile\nexport const updateProfileSchema = z.object({\n  name: z.string().min(3, 'Name must be at leaast 3 characters'),\n  email: z.string().min(3, 'Email must be at leaast 3 characters'),\n});\n\n// Schema to update users\nexport const updateUserSchema = updateProfileSchema.extend({\n  id: z.string().min(1, 'ID is required'),\n  role: z.string().min(1, 'Role is required'),\n});\n\n// Schema to insert reviews\nexport const insertReviewSchema = z.object({\n  title: z.string().min(3, 'Title must be at least 3 characters'),\n  description: z.string().min(3, 'Description must be at least 3 characters'),\n  productId: z.string().min(1, 'Product is required'),\n  userId: z.string().min(1, 'User is required'),\n  rating: z.coerce\n    .number()\n    .int()\n    .min(1, 'Rating must be at least 1')\n    .max(5, 'Rating must be at most 5'),\n});\n"
  },
  {
    "path": "middleware.ts",
    "content": "import NextAuth from 'next-auth';\nimport { authConfig } from './auth.config';\n\nexport const { auth: middleware } = NextAuth(authConfig);\n"
  },
  {
    "path": "next.config.ts",
    "content": "import type { NextConfig } from 'next';\n\nconst nextConfig: NextConfig = {\n  images: {\n    remotePatterns: [\n      {\n        protocol: 'https',\n        hostname: 'utfs.io',\n        port: '',\n      },\n    ],\n  },\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"prostore\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\",\n    \"postinstall\": \"prisma generate\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"email\": \"cp .env ./node_modules/react-email && email dev --dir email --port 3001\"\n  },\n  \"dependencies\": {\n    \"@auth/core\": \"0.37.4\",\n    \"@auth/prisma-adapter\": \"^2.7.4\",\n    \"@hookform/resolvers\": \"^3.9.1\",\n    \"@neondatabase/serverless\": \"^0.10.3\",\n    \"@paypal/react-paypal-js\": \"^8.7.0\",\n    \"@prisma/adapter-neon\": \"6.5.0\",\n    \"@radix-ui/react-alert-dialog\": \"^1.1.2\",\n    \"@radix-ui/react-checkbox\": \"^1.1.2\",\n    \"@radix-ui/react-dialog\": \"^1.1.2\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.2\",\n    \"@radix-ui/react-label\": \"^2.1.0\",\n    \"@radix-ui/react-radio-group\": \"^1.2.1\",\n    \"@radix-ui/react-select\": \"^2.1.2\",\n    \"@radix-ui/react-slot\": \"^1.1.0\",\n    \"@radix-ui/react-toast\": \"^1.2.2\",\n    \"@react-email/components\": \"^0.0.31\",\n    \"@stripe/react-stripe-js\": \"^3.0.0\",\n    \"@stripe/stripe-js\": \"^5.2.0\",\n    \"@uploadthing/react\": \"^7.1.2\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"clsx\": \"^2.1.1\",\n    \"embla-carousel-autoplay\": \"^8.5.1\",\n    \"embla-carousel-react\": \"^8.5.1\",\n    \"lucide-react\": \"^0.456.0\",\n    \"next\": \"^15.2.2\",\n    \"next-auth\": \"^5.0.0-beta.25\",\n    \"next-themes\": \"^0.4.3\",\n    \"query-string\": \"^9.1.1\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-email\": \"^3.0.4\",\n    \"react-hook-form\": \"^7.53.2\",\n    \"recharts\": \"^2.14.1\",\n    \"resend\": \"^4.0.1\",\n    \"slugify\": \"^1.6.6\",\n    \"stripe\": \"^17.4.0\",\n    \"tailwind-merge\": \"^2.5.4\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"uploadthing\": \"^7.4.0\",\n    \"vaul\": \"^1.1.1\",\n    \"ws\": \"^8.18.0\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@prisma/client\": \"6.5.0\",\n    \"@types/jest\": \"^29.5.14\",\n    \"@types/node\": \"^22.10.0\",\n    \"@types/react\": \"^18\",\n    \"@types/react-dom\": \"^18\",\n    \"@types/ws\": \"^8.5.13\",\n    \"bufferutil\": \"^4.0.8\",\n    \"dotenv\": \"^16.4.5\",\n    \"eslint\": \"^8\",\n    \"eslint-config-next\": \"15.0.3\",\n    \"jest\": \"^29.7.0\",\n    \"postcss\": \"^8\",\n    \"prisma\": \"6.5.0\",\n    \"tailwindcss\": \"^3.4.1\",\n    \"ts-jest\": \"^29.2.5\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.mjs",
    "content": "/** @type {import('postcss-load-config').Config} */\nconst config = {\n  plugins: {\n    tailwindcss: {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "prisma/migrations/20241116125832_init/migration.sql",
    "content": "-- CreateTable\nCREATE TABLE \"Product\" (\n    \"id\" UUID NOT NULL DEFAULT gen_random_uuid(),\n    \"name\" TEXT NOT NULL,\n    \"slug\" TEXT NOT NULL,\n    \"category\" TEXT NOT NULL,\n    \"images\" TEXT[],\n    \"brand\" TEXT NOT NULL,\n    \"description\" TEXT NOT NULL,\n    \"stock\" INTEGER NOT NULL,\n    \"price\" DECIMAL(12,2) NOT NULL DEFAULT 0,\n    \"rating\" DECIMAL(3,2) NOT NULL DEFAULT 0,\n    \"numReviews\" INTEGER NOT NULL DEFAULT 0,\n    \"isFeatured\" BOOLEAN NOT NULL,\n    \"banner\" TEXT,\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n    CONSTRAINT \"Product_pkey\" PRIMARY KEY (\"id\")\n);\n\n-- CreateIndex\nCREATE UNIQUE INDEX \"product_slug_idx\" ON \"Product\"(\"slug\");\n"
  },
  {
    "path": "prisma/migrations/20241118183645_add_user_based_tables/migration.sql",
    "content": "-- CreateTable\nCREATE TABLE \"User\" (\n    \"id\" UUID NOT NULL DEFAULT gen_random_uuid(),\n    \"name\" TEXT NOT NULL DEFAULT 'NO_NAME',\n    \"email\" TEXT NOT NULL,\n    \"emailVerified\" TIMESTAMP(6),\n    \"image\" TEXT,\n    \"password\" TEXT,\n    \"role\" TEXT NOT NULL DEFAULT 'user',\n    \"address\" JSON,\n    \"paymentMethod\" TEXT,\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updatedAt\" TIMESTAMP(3) NOT NULL,\n\n    CONSTRAINT \"User_pkey\" PRIMARY KEY (\"id\")\n);\n\n-- CreateTable\nCREATE TABLE \"Account\" (\n    \"userId\" UUID NOT NULL,\n    \"type\" TEXT NOT NULL,\n    \"provider\" TEXT NOT NULL,\n    \"providerAccountId\" TEXT NOT NULL,\n    \"refresh_token\" TEXT,\n    \"access_token\" TEXT,\n    \"expires_at\" INTEGER,\n    \"token_type\" TEXT,\n    \"scope\" TEXT,\n    \"id_token\" TEXT,\n    \"session_state\" TEXT,\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updatedAt\" TIMESTAMP(3) NOT NULL,\n\n    CONSTRAINT \"Account_pkey\" PRIMARY KEY (\"provider\",\"providerAccountId\")\n);\n\n-- CreateTable\nCREATE TABLE \"Session\" (\n    \"sessionToken\" TEXT NOT NULL,\n    \"userId\" UUID NOT NULL,\n    \"expires\" TIMESTAMP(6) NOT NULL,\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updatedAt\" TIMESTAMP(3) NOT NULL,\n\n    CONSTRAINT \"Session_pkey\" PRIMARY KEY (\"sessionToken\")\n);\n\n-- CreateTable\nCREATE TABLE \"VerificationToken\" (\n    \"identifier\" TEXT NOT NULL,\n    \"token\" TEXT NOT NULL,\n    \"expires\" TIMESTAMP(3) NOT NULL,\n\n    CONSTRAINT \"VerificationToken_pkey\" PRIMARY KEY (\"identifier\",\"token\")\n);\n\n-- CreateIndex\nCREATE UNIQUE INDEX \"user_email_idx\" ON \"User\"(\"email\");\n\n-- AddForeignKey\nALTER TABLE \"Account\" ADD CONSTRAINT \"Account_userId_fkey\" FOREIGN KEY (\"userId\") REFERENCES \"User\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n-- AddForeignKey\nALTER TABLE \"Session\" ADD CONSTRAINT \"Session_userId_fkey\" FOREIGN KEY (\"userId\") REFERENCES \"User\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n"
  },
  {
    "path": "prisma/migrations/20241121210251_add_cart/migration.sql",
    "content": "-- CreateTable\nCREATE TABLE \"Cart\" (\n    \"id\" UUID NOT NULL DEFAULT gen_random_uuid(),\n    \"userId\" UUID,\n    \"sessionCartId\" TEXT NOT NULL,\n    \"items\" JSON[] DEFAULT ARRAY[]::JSON[],\n    \"itemsPrice\" DECIMAL(12,2) NOT NULL,\n    \"totalPrice\" DECIMAL(12,2) NOT NULL,\n    \"shippingPrice\" DECIMAL(12,2) NOT NULL,\n    \"taxPrice\" DECIMAL(12,2) NOT NULL,\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n    CONSTRAINT \"Cart_pkey\" PRIMARY KEY (\"id\")\n);\n\n-- AddForeignKey\nALTER TABLE \"Cart\" ADD CONSTRAINT \"Cart_userId_fkey\" FOREIGN KEY (\"userId\") REFERENCES \"User\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n"
  },
  {
    "path": "prisma/migrations/20241125173259_add_order/migration.sql",
    "content": "-- CreateTable\nCREATE TABLE \"Order\" (\n    \"id\" UUID NOT NULL DEFAULT gen_random_uuid(),\n    \"userId\" UUID NOT NULL,\n    \"shippingAddress\" JSON NOT NULL,\n    \"paymentMethod\" TEXT NOT NULL,\n    \"paymentResult\" JSON,\n    \"itemsPrice\" DECIMAL(12,2) NOT NULL,\n    \"shippingPrice\" DECIMAL(12,2) NOT NULL,\n    \"taxPrice\" DECIMAL(12,2) NOT NULL,\n    \"totalPrice\" DECIMAL(12,2) NOT NULL,\n    \"isPaid\" BOOLEAN NOT NULL DEFAULT false,\n    \"paidAt\" TIMESTAMP(6),\n    \"isDelivered\" BOOLEAN NOT NULL DEFAULT false,\n    \"deliveredAt\" TIMESTAMP(6),\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n    CONSTRAINT \"Order_pkey\" PRIMARY KEY (\"id\")\n);\n\n-- CreateTable\nCREATE TABLE \"OrderItem\" (\n    \"orderId\" UUID NOT NULL,\n    \"productId\" UUID NOT NULL,\n    \"qty\" INTEGER NOT NULL,\n    \"price\" DECIMAL(12,2) NOT NULL,\n    \"name\" TEXT NOT NULL,\n    \"slug\" TEXT NOT NULL,\n    \"image\" TEXT NOT NULL,\n\n    CONSTRAINT \"orderitems_orderId_productId_pk\" PRIMARY KEY (\"orderId\",\"productId\")\n);\n\n-- AddForeignKey\nALTER TABLE \"Order\" ADD CONSTRAINT \"Order_userId_fkey\" FOREIGN KEY (\"userId\") REFERENCES \"User\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n-- AddForeignKey\nALTER TABLE \"OrderItem\" ADD CONSTRAINT \"OrderItem_orderId_fkey\" FOREIGN KEY (\"orderId\") REFERENCES \"Order\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n-- AddForeignKey\nALTER TABLE \"OrderItem\" ADD CONSTRAINT \"OrderItem_productId_fkey\" FOREIGN KEY (\"productId\") REFERENCES \"Product\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n"
  },
  {
    "path": "prisma/migrations/20241205162619_add_featured_default/migration.sql",
    "content": "-- AlterTable\nALTER TABLE \"Product\" ALTER COLUMN \"isFeatured\" SET DEFAULT false;\n"
  },
  {
    "path": "prisma/migrations/20241209181915_add_review/migration.sql",
    "content": "-- CreateTable\nCREATE TABLE \"Review\" (\n    \"id\" UUID NOT NULL DEFAULT gen_random_uuid(),\n    \"userId\" UUID NOT NULL,\n    \"productId\" UUID NOT NULL,\n    \"rating\" INTEGER NOT NULL,\n    \"title\" TEXT NOT NULL,\n    \"description\" TEXT NOT NULL,\n    \"isVerifiedPurchase\" BOOLEAN NOT NULL DEFAULT true,\n    \"createdAt\" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n    CONSTRAINT \"Review_pkey\" PRIMARY KEY (\"id\")\n);\n\n-- AddForeignKey\nALTER TABLE \"Review\" ADD CONSTRAINT \"Review_productId_fkey\" FOREIGN KEY (\"productId\") REFERENCES \"Product\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n\n-- AddForeignKey\nALTER TABLE \"Review\" ADD CONSTRAINT \"Review_userId_fkey\" FOREIGN KEY (\"userId\") REFERENCES \"User\"(\"id\") ON DELETE CASCADE ON UPDATE CASCADE;\n"
  },
  {
    "path": "prisma/migrations/migration_lock.toml",
    "content": "# Please do not edit this file manually\n# It should be added in your version-control system (i.e. Git)\nprovider = \"postgresql\""
  },
  {
    "path": "prisma/schema.prisma",
    "content": "generator client {\n  provider        = \"prisma-client-js\"\n  previewFeatures = [\"driverAdapters\"]\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Product {\n  id          String      @id @default(dbgenerated(\"gen_random_uuid()\")) @db.Uuid\n  name        String\n  slug        String      @unique(map: \"product_slug_idx\")\n  category    String\n  images      String[]\n  brand       String\n  description String\n  stock       Int\n  price       Decimal     @default(0) @db.Decimal(12, 2)\n  rating      Decimal     @default(0) @db.Decimal(3, 2)\n  numReviews  Int         @default(0)\n  isFeatured  Boolean     @default(false)\n  banner      String?\n  createdAt   DateTime    @default(now()) @db.Timestamp(6)\n  OrderItem   OrderItem[]\n  Review      Review[]\n}\n\nmodel User {\n  id            String    @id @default(dbgenerated(\"gen_random_uuid()\")) @db.Uuid\n  name          String    @default(\"NO_NAME\")\n  email         String    @unique(map: \"user_email_idx\")\n  emailVerified DateTime? @db.Timestamp(6)\n  image         String?\n  password      String?\n  role          String    @default(\"user\")\n  address       Json?     @db.Json\n  paymentMethod String?\n  createdAt     DateTime  @default(now()) @db.Timestamp(6)\n  updatedAt     DateTime  @updatedAt\n  account       Account[]\n  session       Session[]\n  Cart          Cart[]\n  Order         Order[]\n  Review        Review[]\n}\n\nmodel Account {\n  userId            String  @db.Uuid\n  type              String\n  provider          String\n  providerAccountId String\n  refresh_token     String?\n  access_token      String?\n  expires_at        Int?\n  token_type        String?\n  scope             String?\n  id_token          String?\n  session_state     String?\n\n  createdAt DateTime @default(now()) @db.Timestamp(6)\n  updatedAt DateTime @updatedAt\n\n  user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@id([provider, providerAccountId])\n}\n\nmodel Session {\n  sessionToken String   @id\n  userId       String   @db.Uuid\n  expires      DateTime @db.Timestamp(6)\n  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  createdAt DateTime @default(now()) @db.Timestamp(6)\n  updatedAt DateTime @updatedAt\n}\n\nmodel VerificationToken {\n  identifier String\n  token      String\n  expires    DateTime\n\n  @@id([identifier, token])\n}\n\nmodel Cart {\n  id            String   @id @default(dbgenerated(\"gen_random_uuid()\")) @db.Uuid\n  userId        String?  @db.Uuid\n  sessionCartId String\n  items         Json[]   @default([]) @db.Json\n  itemsPrice    Decimal  @db.Decimal(12, 2)\n  totalPrice    Decimal  @db.Decimal(12, 2)\n  shippingPrice Decimal  @db.Decimal(12, 2)\n  taxPrice      Decimal  @db.Decimal(12, 2)\n  createdAt     DateTime @default(now()) @db.Timestamp(6)\n  user          User?    @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Order {\n  id              String      @id @default(dbgenerated(\"gen_random_uuid()\")) @db.Uuid\n  userId          String      @db.Uuid\n  shippingAddress Json        @db.Json\n  paymentMethod   String\n  paymentResult   Json?       @db.Json\n  itemsPrice      Decimal     @db.Decimal(12, 2)\n  shippingPrice   Decimal     @db.Decimal(12, 2)\n  taxPrice        Decimal     @db.Decimal(12, 2)\n  totalPrice      Decimal     @db.Decimal(12, 2)\n  isPaid          Boolean     @default(false)\n  paidAt          DateTime?   @db.Timestamp(6)\n  isDelivered     Boolean     @default(false)\n  deliveredAt     DateTime?   @db.Timestamp(6)\n  createdAt       DateTime    @default(now()) @db.Timestamp(6)\n  user            User        @relation(fields: [userId], references: [id], onDelete: Cascade)\n  orderitems      OrderItem[]\n}\n\nmodel OrderItem {\n  orderId   String  @db.Uuid\n  productId String  @db.Uuid\n  qty       Int\n  price     Decimal @db.Decimal(12, 2)\n  name      String\n  slug      String\n  image     String\n  order     Order   @relation(fields: [orderId], references: [id], onDelete: Cascade)\n  product   Product @relation(fields: [productId], references: [id], onDelete: Cascade)\n\n  @@id([orderId, productId], map: \"orderitems_orderId_productId_pk\")\n}\n\nmodel Review {\n  id                 String   @id @default(dbgenerated(\"gen_random_uuid()\")) @db.Uuid\n  userId             String   @db.Uuid\n  productId          String   @db.Uuid\n  rating             Int\n  title              String\n  description        String\n  isVerifiedPurchase Boolean  @default(true)\n  createdAt          DateTime @default(now()) @db.Timestamp(6)\n  product            Product  @relation(fields: [productId], references: [id], onDelete: Cascade)\n  user               User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n"
  },
  {
    "path": "tailwind.config.ts",
    "content": "import type { Config } from \"tailwindcss\";\n\nexport default {\n    darkMode: [\"class\"],\n    content: [\n    \"./pages/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"./components/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"./app/**/*.{js,ts,jsx,tsx,mdx}\",\n  ],\n  theme: {\n  \textend: {\n  \t\tcolors: {\n  \t\t\tbackground: 'hsl(var(--background))',\n  \t\t\tforeground: 'hsl(var(--foreground))',\n  \t\t\tcard: {\n  \t\t\t\tDEFAULT: 'hsl(var(--card))',\n  \t\t\t\tforeground: 'hsl(var(--card-foreground))'\n  \t\t\t},\n  \t\t\tpopover: {\n  \t\t\t\tDEFAULT: 'hsl(var(--popover))',\n  \t\t\t\tforeground: 'hsl(var(--popover-foreground))'\n  \t\t\t},\n  \t\t\tprimary: {\n  \t\t\t\tDEFAULT: 'hsl(var(--primary))',\n  \t\t\t\tforeground: 'hsl(var(--primary-foreground))'\n  \t\t\t},\n  \t\t\tsecondary: {\n  \t\t\t\tDEFAULT: 'hsl(var(--secondary))',\n  \t\t\t\tforeground: 'hsl(var(--secondary-foreground))'\n  \t\t\t},\n  \t\t\tmuted: {\n  \t\t\t\tDEFAULT: 'hsl(var(--muted))',\n  \t\t\t\tforeground: 'hsl(var(--muted-foreground))'\n  \t\t\t},\n  \t\t\taccent: {\n  \t\t\t\tDEFAULT: 'hsl(var(--accent))',\n  \t\t\t\tforeground: 'hsl(var(--accent-foreground))'\n  \t\t\t},\n  \t\t\tdestructive: {\n  \t\t\t\tDEFAULT: 'hsl(var(--destructive))',\n  \t\t\t\tforeground: 'hsl(var(--destructive-foreground))'\n  \t\t\t},\n  \t\t\tborder: 'hsl(var(--border))',\n  \t\t\tinput: 'hsl(var(--input))',\n  \t\t\tring: 'hsl(var(--ring))',\n  \t\t\tchart: {\n  \t\t\t\t'1': 'hsl(var(--chart-1))',\n  \t\t\t\t'2': 'hsl(var(--chart-2))',\n  \t\t\t\t'3': 'hsl(var(--chart-3))',\n  \t\t\t\t'4': 'hsl(var(--chart-4))',\n  \t\t\t\t'5': 'hsl(var(--chart-5))'\n  \t\t\t}\n  \t\t},\n  \t\tborderRadius: {\n  \t\t\tlg: 'var(--radius)',\n  \t\t\tmd: 'calc(var(--radius) - 2px)',\n  \t\t\tsm: 'calc(var(--radius) - 4px)'\n  \t\t}\n  \t}\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n} satisfies Config;\n"
  },
  {
    "path": "tests/paypal.test.ts",
    "content": "import { generateAccessToken, paypal } from '../lib/paypal';\n\n// Test to generate access token from paypal\ntest('generates token from paypal', async () => {\n  const tokenResponse = await generateAccessToken();\n  console.log(tokenResponse);\n  expect(typeof tokenResponse).toBe('string');\n  expect(tokenResponse.length).toBeGreaterThan(0);\n});\n\n// Test to create a paypal order\ntest('creates a paypal order', async () => {\n  const token = await generateAccessToken();\n  const price = 10.0;\n\n  const orderResponse = await paypal.createOrder(price);\n  console.log(orderResponse);\n\n  expect(orderResponse).toHaveProperty('id');\n  expect(orderResponse).toHaveProperty('status');\n  expect(orderResponse.status).toBe('CREATED');\n});\n\n// Test to capture payment with mock order\ntest('simulate capturing a payment from an order', async () => {\n  const orderId = '100';\n\n  const mockCapturePayment = jest\n    .spyOn(paypal, 'capturePayment')\n    .mockResolvedValue({\n      status: 'COMPLETED',\n    });\n\n  const captureResponse = await paypal.capturePayment(orderId);\n  expect(captureResponse).toHaveProperty('status', 'COMPLETED');\n\n  mockCapturePayment.mockRestore();\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "types/index.ts",
    "content": "import { z } from 'zod';\nimport {\n  insertProductSchema,\n  insertCartSchema,\n  cartItemSchema,\n  shippingAddressSchema,\n  insertOrderItemSchema,\n  insertOrderSchema,\n  paymentResultSchema,\n  insertReviewSchema,\n} from '@/lib/validators';\n\nexport type Product = z.infer<typeof insertProductSchema> & {\n  id: string;\n  rating: string;\n  numReviews: number;\n  createdAt: Date;\n};\n\nexport type Cart = z.infer<typeof insertCartSchema>;\nexport type CartItem = z.infer<typeof cartItemSchema>;\nexport type ShippingAddress = z.infer<typeof shippingAddressSchema>;\nexport type OrderItem = z.infer<typeof insertOrderItemSchema>;\nexport type Order = z.infer<typeof insertOrderSchema> & {\n  id: string;\n  createdAt: Date;\n  isPaid: boolean;\n  paidAt: Date | null;\n  isDelivered: boolean;\n  deliveredAt: Date | null;\n  orderitems: OrderItem[];\n  user: { name: string; email: string };\n  paymentResult: PaymentResult;\n};\nexport type PaymentResult = z.infer<typeof paymentResultSchema>;\nexport type Review = z.infer<typeof insertReviewSchema> & {\n  id: string;\n  createdAt: Date;\n  user?: { name: string };\n};\n"
  },
  {
    "path": "types/next-auth.d.ts",
    "content": "import { DefaultSession } from 'next-auth';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport NextAuth from 'next-auth';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport { JWT } from 'next-auth/jwt';\n\ndeclare module 'next-auth/jwt' {\n  /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */\n  interface JWT {\n    sub: string;\n    role: string;\n    name: string;\n  }\n}\n\ndeclare module 'next-auth' {\n  /**\n   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context\n   */\n  interface Session {\n    user: {\n      role: string;\n    } & DefaultSession['user'];\n  }\n\n  interface User {\n    role: string;\n  }\n}\n"
  }
]