[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "dist/*\n.cache\npublic\nnode_modules\n*.esm.js\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/eslintrc\",\n  \"root\": true,\n  \"extends\": [\n    \"next/core-web-vitals\",\n    \"prettier\",\n    \"plugin:tailwindcss/recommended\"\n  ],\n  \"plugins\": [\"tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss/classnames-order\": \"off\",\n    \"@next/next/no-html-link-for-pages\": \"off\",\n    \"react/jsx-key\": \"off\",\n    \"tailwindcss/no-custom-classname\": \"off\",\n    \"react-hooks/exhaustive-deps\": \"off\"\n  },\n  \"settings\": {\n    \"tailwindcss\": {\n      \"callees\": [\"cn\"]\n    }\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# testing\ncoverage\n\n# next.js\n.next/\nout/\nbuild\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# turbo\n.turbo\n\n.contentlayer\n.env"
  },
  {
    "path": ".prettierignore",
    "content": "cache\n.cache\npackage.json\npackage-lock.json\npublic\nCHANGELOG.md\n.yarn\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"node_modules\\\\typescript\\\\lib\",\n  \"typescript.enablePromptUseWorkspaceTsdk\": true\n}"
  },
  {
    "path": "README.md",
    "content": "# Next alt generator\n\nA Next.js 13 project for generating image alt tags automatically and in bulk.\n\n## Features\n\n- Radix UI Primitives\n- Tailwind CSS\n- Fonts with `@next/font`\n- Icons from [Lucide](https://lucide.dev)\n- Dark mode with `next-themes`\n- Automatic import sorting with `@ianvs/prettier-plugin-sort-imports`\n\n## Tailwind CSS Features\n\n- Class merging with `taiwind-merge`\n- Animation with `tailwindcss-animate`\n- Conditional classes with `clsx`\n- Variants with `class-variance-authority`\n- Automatic class sorting with `eslint-plugin-tailwindcss`\n\n## Import Sort\n\nThe starter comes with `@ianvs/prettier-plugin-sort-imports` for automatically sort your imports.\n\n### Input\n\n```tsx\nimport * as React from \"react\"\nimport Link from \"next/link\"\n\nimport { siteConfig } from \"@/config/site\"\nimport { buttonVariants } from \"@/components/ui/button\"\nimport \"@/styles/globals.css\"\nimport { twMerge } from \"tailwind-merge\"\n\nimport { NavItem } from \"@/types/nav\"\nimport { cn } from \"@/lib/utils\"\n```\n\n### Output\n\n```tsx\nimport * as React from \"react\"\n// React is always first.\nimport Link from \"next/link\"\n// Followed by next modules.\nimport { twMerge } from \"tailwind-merge\"\n\n// Followed by third-party modules\n// Space\nimport \"@/styles/globals.css\"\n// styles\nimport { NavItem } from \"@/types/nav\"\n// types\nimport { siteConfig } from \"@/config/site\"\n// config\nimport { cn } from \"@/lib/utils\"\n// lib\nimport { buttonVariants } from \"@/components/ui/button\"\n\n// components\n```\n\n### Class Merging\n\nThe `cn` util handles conditional classes and class merging.\n\n### Input\n\n```ts\ncn(\"px-2 bg-slate-100 py-2 bg-slate-200\")\n// Outputs `p-2 bg-slate-200`\n```\n\n## License & Credits\n\nLicensed under the [MIT license](https://opensource.org/license/mit/).\nBoilerplate project template made by [shadcn](https://github.com/shadcn/next-template)\n"
  },
  {
    "path": "next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n"
  },
  {
    "path": "next.config.mjs",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  reactStrictMode: true,\n  experimental: {\n    appDir: true,\n    fontLoaders: [\n      {\n        loader: \"@next/font/google\",\n        options: { subsets: [\"latin\"] },\n      },\n    ],\n  },\n  images: {\n    domains: [\"image-to-alt.s3.eu-central-1.amazonaws.com\"],\n  },\n}\n\nexport default nextConfig\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"next-template\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\",\n    \"preview\": \"next build && next start\"\n  },\n  \"dependencies\": {\n    \"@next/font\": \"^13.1.6\",\n    \"@radix-ui/react-accessible-icon\": \"^1.0.1\",\n    \"@radix-ui/react-accordion\": \"^1.1.0\",\n    \"@radix-ui/react-alert-dialog\": \"^1.0.2\",\n    \"@radix-ui/react-aspect-ratio\": \"^1.0.1\",\n    \"@radix-ui/react-avatar\": \"^1.0.1\",\n    \"@radix-ui/react-checkbox\": \"^1.0.1\",\n    \"@radix-ui/react-collapsible\": \"^1.0.1\",\n    \"@radix-ui/react-context-menu\": \"^2.1.1\",\n    \"@radix-ui/react-dialog\": \"^1.0.2\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.0.1\",\n    \"@radix-ui/react-hover-card\": \"^1.0.3\",\n    \"@radix-ui/react-label\": \"^2.0.0\",\n    \"@radix-ui/react-menubar\": \"^1.0.0\",\n    \"@radix-ui/react-navigation-menu\": \"^1.1.1\",\n    \"@radix-ui/react-popover\": \"^1.0.2\",\n    \"@radix-ui/react-progress\": \"^1.0.1\",\n    \"@radix-ui/react-radio-group\": \"^1.1.0\",\n    \"@radix-ui/react-scroll-area\": \"^1.0.2\",\n    \"@radix-ui/react-select\": \"^1.2.0\",\n    \"@radix-ui/react-separator\": \"^1.0.1\",\n    \"@radix-ui/react-slider\": \"^1.1.0\",\n    \"@radix-ui/react-slot\": \"^1.0.1\",\n    \"@radix-ui/react-switch\": \"^1.0.1\",\n    \"@radix-ui/react-tabs\": \"^1.0.2\",\n    \"@radix-ui/react-toast\": \"^1.1.2\",\n    \"@radix-ui/react-toggle-group\": \"^1.0.1\",\n    \"@radix-ui/react-tooltip\": \"^1.0.3\",\n    \"aws-sdk\": \"^2.1318.0\",\n    \"axios\": \"^1.3.3\",\n    \"class-variance-authority\": \"^0.4.0\",\n    \"clsx\": \"^1.2.1\",\n    \"lucide-react\": \"0.105.0-alpha.4\",\n    \"nanoid\": \"^4.0.1\",\n    \"next\": \"^13.1.6\",\n    \"next-themes\": \"^0.2.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"sharp\": \"^0.31.3\",\n    \"tailwind-merge\": \"^1.8.0\",\n    \"tailwindcss-animate\": \"^1.0.5\",\n    \"zod\": \"^3.20.6\"\n  },\n  \"devDependencies\": {\n    \"@ianvs/prettier-plugin-sort-imports\": \"^3.7.1\",\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^18.0.22\",\n    \"@types/react-dom\": \"^18.0.7\",\n    \"autoprefixer\": \"^10.4.13\",\n    \"eslint\": \"^8.31.0\",\n    \"eslint-config-next\": \"13.0.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-react\": \"^7.31.11\",\n    \"eslint-plugin-tailwindcss\": \"^3.8.0\",\n    \"postcss\": \"^8.4.14\",\n    \"prettier\": \"^2.7.1\",\n    \"tailwindcss\": \"^3.1.7\",\n    \"typescript\": \"^4.5.3\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "/** @type {import('prettier').Config} */\nmodule.exports = {\n  endOfLine: \"lf\",\n  semi: false,\n  singleQuote: true,\n  tabWidth: 2,\n  trailingComma: \"es5\",\n  importOrder: [\n    \"^(react/(.*)$)|^(react$)\",\n    \"^(next/(.*)$)|^(next$)\",\n    \"<THIRD_PARTY_MODULES>\",\n    \"\",\n    \"^types$\",\n    \"^@/types/(.*)$\",\n    \"^@/config/(.*)$\",\n    \"^@/lib/(.*)$\",\n    \"^@/components/(.*)$\",\n    \"^@/styles/(.*)$\",\n    \"^[./]\",\n  ],\n  importOrderSeparation: false,\n  importOrderSortSpecifiers: true,\n  importOrderBuiltinModulesToTop: true,\n  importOrderParserPlugins: [\"typescript\", \"jsx\", \"decorators-legacy\"],\n  importOrderMergeDuplicateImports: true,\n  importOrderCombineTypeAndValueImports: true,\n  plugins: [\"@ianvs/prettier-plugin-sort-imports\"],\n}\n"
  },
  {
    "path": "src/app/head.tsx",
    "content": "import { FC } from 'react'\n\nconst head: FC = () => {\n  return <title>ImageToAlt - Generate alt tags from images</title>\n}\n\nexport default head\n"
  },
  {
    "path": "src/app/layout.tsx",
    "content": "import { Inter as FontSans } from '@next/font/google'\n\nimport '@/styles/globals.css'\nimport { Toaster } from '@/ui/toaster'\n\nimport { cn } from '@/lib/utils'\nimport { SiteHeader } from '../components/site-header'\nimport { TooltipProvider } from '../components/ui/tooltip'\n\nconst fontSans = FontSans({\n  subsets: ['latin'],\n  variable: '--font-inter',\n})\n\ninterface RootLayoutProps {\n  children: React.ReactNode\n}\n\nexport default function RootLayout({ children }: RootLayoutProps) {\n  return (\n    <>\n      <html\n        lang=\"en\"\n        className={cn(\n          'dark bg-white font-sans text-slate-900 antialiased',\n          fontSans.variable\n        )}\n      >\n        <body className=\"min-h-screen bg-white font-sans text-slate-900 antialiased dark:bg-slate-900 dark:text-slate-50\">\n          <Toaster />\n          <SiteHeader />\n          <TooltipProvider>\n            <main>{children}</main>\n          </TooltipProvider>\n        </body>\n      </html>\n    </>\n  )\n}\n"
  },
  {
    "path": "src/app/page.tsx",
    "content": "'use client'\n\nimport { FC } from 'react'\nimport { Button, buttonVariants } from '@/ui/button'\nimport { FileInput } from '@/ui/file-input'\n\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipTrigger,\n} from '@/components/ui/tooltip'\n\nexport const metadata = {\n  title: 'ImageToAlt - Home',\n}\n\nconst page: FC = () => {\n  return (\n    <section className=\"container grid items-center gap-6 pt-6 pb-8 md:py-10\">\n      <div className=\"flex max-w-[980px] flex-col items-start gap-2\">\n        <h1 className=\"text-3xl font-extrabold leading-tight tracking-tighter sm:text-3xl md:text-5xl lg:text-6xl\">\n          Easily create alt-descriptions <br className=\"hidden sm:inline\" />\n          for your images.\n        </h1>\n        <p className=\"max-w-[700px] text-lg text-slate-700 dark:text-slate-400 sm:text-xl\">\n          Bulk-generate SEO-optimized alt-descriptions that you can copy and\n          paste into your app. Free & open-source.\n        </p>\n      </div>\n      <div className=\"flex gap-4\">\n        <FileInput />\n      </div>\n\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <div className=\"w-fit\">\n            <Button\n              disabled\n              className={buttonVariants({ size: 'lg', className: 'w-fit' })}\n            >\n              Download as CSV\n            </Button>\n          </div>\n        </TooltipTrigger>\n        <TooltipContent>\n          <p>Available soon</p>\n        </TooltipContent>\n      </Tooltip>\n\n      {/* Legal disclaimers */}\n      <div className=\"flex flex-col gap-4 mt-12\">\n        <p className=\"text-slate-400 text-sm\">\n          All images are used solely for alt-generation and are automatically\n          deleted after 24h.\n        </p>\n        <div className=\"flex items-center gap-4\">\n          <Button\n            href=\"/terms\"\n            className={buttonVariants({ variant: 'link', size: 'sm' })}\n          >\n            Terms\n          </Button>\n          <Button\n            href=\"/privacy-policy\"\n            className={buttonVariants({ variant: 'link', size: 'sm' })}\n          >\n            Privacy Policy\n          </Button>\n        </div>\n      </div>\n    </section>\n  )\n}\n\nexport default page\n"
  },
  {
    "path": "src/app/privacy-policy/head.tsx",
    "content": "import { FC } from 'react'\n\nconst head: FC = () => {\n  return <title>Privacy Policy - ImageToAlt</title>\n}\n\nexport default head\n"
  },
  {
    "path": "src/app/privacy-policy/page.tsx",
    "content": "import { FC } from 'react'\nimport Link from 'next/link'\n\nconst page: FC = () => {\n  return (\n    <div className=\"text-slate-300\">\n      <div className=\"max-w-4xl mx-auto py-8 px-4 sm:px-6 lg:px-8\">\n        <h1 className=\"text-2xl font-bold mb-4 text-slate-100\">\n          ImageToAlt Privacy Policy\n        </h1>\n        <p className=\"mb-4\">\n          ImageToAlt is a free online service that provides a simple way to\n          generate an alt tag (a description of what is visible on an image,\n          determined by a machine learning algorithm) from an image. In order to\n          provide this service, we need to collect and store images on our\n          servers.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Information We Collect</h2>\n        <p className=\"mb-4\">\n          When you use the App, we automatically collect certain information\n          about your device, including information about your web browser, IP\n          address, time zone, and some of the cookies that are installed on your\n          device. We refer to this automatically-collected information as\n          &quot;Device Information&quot;.\n        </p>\n        <p className=\"mb-4\">\n          We collect Device Information using the following technologies:\n        </p>\n        <ul className=\"list-disc ml-8 mb-4\">\n          <li>\n            Cookies: Cookies are data files that are placed on your device or\n            computer and often include an anonymous unique identifier.\n          </li>\n          <li>\n            Log files: Log files track actions occurring on the App, and collect\n            data including your IP address, browser type, Internet service\n            provider, referring/exit pages, and date/time stamps.\n          </li>\n        </ul>\n        <p className=\"mb-4\">\n          When you upload an image to the App, we collect the image itself. We\n          use the image to generate an alt tag and store the alt tag. The image\n          is then automatically deleted after 24 hours.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">How We Use Your Information</h2>\n        <p className=\"mb-4\">\n          We use the images you upload to our servers solely for the purpose of\n          generating an alt tag and serving it back to you. We do not use your\n          images for any other purpose. To provide this service, we use a\n          machine learning algorithm provided by Replicate, Inc. You can read\n          more about how Replicate, Inc. uses your data{' '}\n          <Link\n            className=\"underline text-blue-400\"\n            href=\"https://replicate.com/terms\"\n          >\n            here\n          </Link>\n          . We require all third-party providers to have adequate technical and\n          organizational measures in place to ensure the security of user data.\n          We do not share user data with any other third parties.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">\n          How We Protect Your Information\n        </h2>\n        <p className=\"mb-4\">\n          We take the security of your information very seriously. All images\n          uploaded to our servers are stored in a secure location in Germany. We\n          do not share your images with any third parties. We also automatically\n          delete all images from our servers after 24 hours.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Use of Cookies</h2>\n        <p className=\"mb-4\">\n          We do not use any cookies to track user behavior. We only use session\n          cookies to manage your session on our website.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">\n          Changes to Our Privacy Policy\n        </h2>\n        <p className=\"mb-4\">\n          We reserve the right to make changes to this Privacy Policy at any\n          time. Any changes will be posted on this page, so please check back\n          periodically for updates.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Contact Us</h2>\n        <p className=\"mb-4\">\n          If you have any questions about this Privacy Policy, please contact us\n          at admin@wordful.ai.\n        </p>\n      </div>\n    </div>\n  )\n}\n\nexport default page\n"
  },
  {
    "path": "src/app/terms/head.tsx",
    "content": "import { FC } from 'react'\n\nconst head: FC = () => {\n  return <title>Terms and Conditions - ImageToAlt</title>\n}\n\nexport default head\n"
  },
  {
    "path": "src/app/terms/page.tsx",
    "content": "import { FC } from 'react'\nimport Head from 'next/head'\n\nconst page: FC = () => {\n  return (\n    <div className=\"text-slate-300\">\n      <Head>\n        <title>ImageToAlt - Terms and Conditions</title>\n      </Head>\n\n      <div className=\"max-w-4xl mx-auto py-8 px-4 sm:px-6 lg:px-8\">\n        <h1 className=\"text-2xl font-bold mb-4\">\n          ImageToAlt Terms and Conditions\n        </h1>\n        <p className=\"mb-4\">\n          These terms and conditions (&quot;Terms&quot;) apply to your use of\n          the ImageToAlt app (&quot;App&quot;) and the alt tag generation\n          service (&quot;Service&quot;) provided by ImageToAlt (&quot;we&quot;\n          or &quot;us&quot;). By using the App or the Service, you agree to be\n          bound by these Terms.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">\n          Use of the App and the Service\n        </h2>\n        <p className=\"mb-4\">\n          The App and the Service are provided for informational purposes only.\n          You may use the App and the Service at your own risk, and we shall not\n          be liable for any damages or harm that may arise from your use of the\n          App or the Service.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Intellectual Property</h2>\n        <p className=\"mb-4\">\n          The App and the Service, including any content or materials made\n          available through the App or the Service, are protected by copyright\n          and other intellectual property laws. You may not copy, modify,\n          distribute, sell, or lease any part of the App or the Service without\n          our prior written consent.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Disclaimer of Liability</h2>\n        <p className=\"mb-4\">\n          The App and the Service are provided &quot;as is&quot; and without warranty of\n          any kind. We make no representations or warranties of any kind,\n          express or implied, about the completeness, accuracy, reliability,\n          suitability or availability with respect to the App or the Service or\n          the information, products, services, or related graphics contained in\n          the App or the Service for any purpose. To the fullest extent\n          permitted by law, we disclaim any and all warranties, express or\n          implied, including, but not limited to, implied warranties of\n          merchantability and fitness for a particular purpose.\n        </p>\n        <p className=\"mb-4\">\n          In no event shall ImageToAlt be liable for any direct, indirect,\n          incidental, consequential, special or exemplary damages, including,\n          but not limited to, damages for loss of profits, goodwill, use, data\n          or other intangible losses resulting from the use of or inability to\n          use the App or the Service.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Indemnification</h2>\n        <p className=\"mb-4\">\n          You agree to indemnify and hold ImageToAlt, its affiliates, officers,\n          agents, and other partners and employees, harmless from any loss,\n          liability, claim or demand, including reasonable attorneys&apos; fees, made\n          by any third party due to or arising out of your use of the App or the\n          Service.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Termination</h2>\n        <p className=\"mb-4\">\n          We may terminate your access to the App and the Service at any time,\n          without cause or notice.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Governing Law</h2>\n        <p className=\"mb-4\">\n          These Terms and your use of the App and the Service shall be governed\n          by and construed in accordance with the laws of Germany, without\n          giving effect to any principles of conflicts of law.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Changes to these Terms</h2>\n        <p className=\"mb-4\">\n          We reserve the right to modify these Terms at any time. If we make\n          changes to these Terms, we will post the revised Terms on the App and\n          update the &quot;Last Updated&quot; date at the top of these Terms. By\n          continuing to use the App and the Service after the revised Terms\n          become effective, you agree to be bound by the revised Terms.\n        </p>\n        <h2 className=\"text-xl font-bold mb-2\">Contact Us</h2>\n        <p className=\"mb-4\">\n          If you have any questions about these Terms or the App or the Service,\n          please contact us at admin@wordful.ai.\n        </p>\n        <p className=\"text-sm\">Last Updated: Feb 20th, 2023</p>\n      </div>\n    </div>\n  )\n}\n\nexport default page\n"
  },
  {
    "path": "src/components/icons.tsx",
    "content": "import {\n  Laptop,\n  LucideProps,\n  Moon,\n  SunMedium,\n  type Icon as LucideIcon,\n} from 'lucide-react'\n\nexport type Icon = LucideIcon\n\nexport const Icons = {\n  sun: SunMedium,\n  moon: Moon,\n  laptop: Laptop,\n  youtube: (props: LucideProps) => (\n    <svg\n      {...props}\n      viewBox=\"0 -77 512.00213 512\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"m501.453125 56.09375c-5.902344-21.933594-23.195313-39.222656-45.125-45.128906-40.066406-10.964844-200.332031-10.964844-200.332031-10.964844s-160.261719 0-200.328125 10.546875c-21.507813 5.902344-39.222657 23.617187-45.125 45.546875-10.542969 40.0625-10.542969 123.148438-10.542969 123.148438s0 83.503906 10.542969 123.148437c5.90625 21.929687 23.195312 39.222656 45.128906 45.128906 40.484375 10.964844 200.328125 10.964844 200.328125 10.964844s160.261719 0 200.328125-10.546875c21.933594-5.902344 39.222656-23.195312 45.128906-45.125 10.542969-40.066406 10.542969-123.148438 10.542969-123.148438s.421875-83.507812-10.546875-123.570312zm0 0\"\n        fill=\"#94a3b8\"\n      />\n      <path\n        d=\"m204.96875 256 133.269531-76.757812-133.269531-76.757813zm0 0\"\n        fill=\"#fff\"\n      />\n    </svg>\n  ),\n  logo: (props: LucideProps) => (\n    <svg {...props} viewBox=\"0 0 512 512\">\n      <path\n        style={{ fill: '#EBF0FA' }}\n        d=\"M446.575,512H65.425C29.35,512,0,482.65,0,446.575V65.425C0,29.35,29.35,0,65.425,0h381.15\n\tC482.65,0,512,29.35,512,65.425v381.149C512,482.65,482.65,512,446.575,512z\"\n      />\n      <path\n        style={{ fill: '#DCE1EB' }}\n        d=\"M446.575,0H256.004v512h190.571C482.65,512,512,482.65,512,446.575V65.425\n\tC512,29.35,482.65,0,446.575,0z\"\n      />\n      <path\n        style={{ fill: '#AAC85A' }}\n        d=\"M410.155,191.597c-0.025-0.032-0.052-0.065-0.078-0.098c-7.117-8.764-17.666-14.122-28.942-14.701\n\tc-11.268-0.57-22.317,3.672-30.295,11.662l-138.007,138.22l-51.59-42.839c-14.959-12.422-36.563-12.293-51.373,0.308\n\tc-0.031,0.027-0.063,0.054-0.094,0.081L0,379.215v67.358c0,36.076,29.35,65.425,65.425,65.425h381.15\n\tc36.076,0,65.425-29.349,65.425-65.425V319.154L410.155,191.597z\"\n      />\n      <path\n        style={{ fill: '#6EAA50' }}\n        d=\"M410.077,191.5c-7.117-8.764-17.666-14.122-28.942-14.701c-11.268-0.57-22.317,3.672-30.295,11.662\n\tl-94.835,94.981V512h190.571C482.65,512,512,482.651,512,446.575V319.154L410.155,191.597\n\tC410.129,191.565,410.103,191.532,410.077,191.5z\"\n      />\n      <path\n        style={{ fill: '#AAC85A' }}\n        d=\"M161.174,208.421c-40.095,0-72.713-32.619-72.713-72.713s32.619-72.713,72.713-72.713\n\ts72.713,32.62,72.713,72.713S201.269,208.421,161.174,208.421z\"\n      />\n    </svg>\n  ),\n  gitHub: (props: LucideProps) => (\n    <svg viewBox=\"0 0 438.549 438.549\" {...props}>\n      <path\n        fill=\"currentColor\"\n        d=\"M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 01-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z\"\n      ></path>\n    </svg>\n  ),\n  plus: (props: LucideProps) => (\n    <svg {...props} viewBox=\"0 0 512 512\">\n      <path d=\"m256 512c-141.164062 0-256-114.835938-256-256s114.835938-256 256-256 256 114.835938 256 256-114.835938 256-256 256zm0-480c-123.519531 0-224 100.480469-224 224s100.480469 224 224 224 224-100.480469 224-224-100.480469-224-224-224zm0 0\" />\n      <path d=\"m368 272h-224c-8.832031 0-16-7.167969-16-16s7.167969-16 16-16h224c8.832031 0 16 7.167969 16 16s-7.167969 16-16 16zm0 0\" />\n      <path d=\"m256 384c-8.832031 0-16-7.167969-16-16v-224c0-8.832031 7.167969-16 16-16s16 7.167969 16 16v224c0 8.832031-7.167969 16-16 16zm0 0\" />\n    </svg>\n  ),\n  redx: (props: LucideProps) => (\n    <svg {...props} viewBox=\"0 0 455.111 455.111\">\n      <circle\n        style={{ fill: '#E24C4B' }}\n        cx=\"227.556\"\n        cy=\"227.556\"\n        r=\"227.556\"\n      />\n      <path\n        style={{ fill: '#D1403F' }}\n        d=\"M455.111,227.556c0,125.156-102.4,227.556-227.556,227.556c-72.533,0-136.533-32.711-177.778-85.333\n\tc38.4,31.289,88.178,49.778,142.222,49.778c125.156,0,227.556-102.4,227.556-227.556c0-54.044-18.489-103.822-49.778-142.222\n\tC422.4,91.022,455.111,155.022,455.111,227.556z\"\n      />\n      <path\n        style={{ fill: '#FFFFFF' }}\n        d=\"M331.378,331.378c-8.533,8.533-22.756,8.533-31.289,0l-72.533-72.533l-72.533,72.533\n\tc-8.533,8.533-22.756,8.533-31.289,0c-8.533-8.533-8.533-22.756,0-31.289l72.533-72.533l-72.533-72.533\n\tc-8.533-8.533-8.533-22.756,0-31.289c8.533-8.533,22.756-8.533,31.289,0l72.533,72.533l72.533-72.533\n\tc8.533-8.533,22.756-8.533,31.289,0c8.533,8.533,8.533,22.756,0,31.289l-72.533,72.533l72.533,72.533\n\tC339.911,308.622,339.911,322.844,331.378,331.378z\"\n      />\n    </svg>\n  ),\n}\n"
  },
  {
    "path": "src/components/main-nav.tsx",
    "content": "import Link from 'next/link'\nimport { NavItem } from '@/src/types/nav'\n\nimport { siteConfig } from '@/config/site'\nimport { cn } from '@/lib/utils'\nimport { Icons } from '@/components/icons'\nimport { Button } from '@/components/ui/button'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\n\ninterface MainNavProps {\n  items?: NavItem[]\n}\n\nexport function MainNav({ items }: MainNavProps) {\n  return (\n    <div className=\"flex gap-6 md:gap-10\">\n      <Link href=\"/\" className=\"hidden items-center space-x-2 md:flex\">\n        <Icons.logo className=\"h-6 w-6\" />\n        <span className=\"hidden font-bold sm:inline-block\">\n          {siteConfig.name}\n        </span>\n      </Link>\n      {items?.length ? (\n        <nav className=\"hidden gap-6 md:flex\">\n          {items?.map(\n            (item, index) =>\n              item.href && (\n                <Link\n                  key={index}\n                  href={item.href}\n                  className={cn(\n                    'flex items-center text-lg font-semibold text-slate-600 hover:text-slate-900 dark:text-slate-100 sm:text-sm',\n                    item.disabled && 'cursor-not-allowed opacity-80'\n                  )}\n                >\n                  {item.title}\n                </Link>\n              )\n          )}\n        </nav>\n      ) : null}\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button\n            variant=\"ghost\"\n            className=\"-ml-4 text-base hover:bg-transparent focus:ring-0 md:hidden\"\n          >\n            <Icons.logo className=\"mr-2 h-4 w-4\" />{' '}\n            <span className=\"font-bold\">Menu</span>\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent\n          align=\"start\"\n          sideOffset={24}\n          className=\"w-[300px] overflow-scroll\"\n        >\n          <DropdownMenuLabel>\n            <Link href=\"/\" className=\"flex items-center\">\n              <Icons.logo className=\"mr-2 h-4 w-4\" /> {siteConfig.name}\n            </Link>\n          </DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          {items?.map(\n            (item, index) =>\n              item.href && (\n                <DropdownMenuItem key={index} asChild>\n                  <Link href={item.href}>{item.title}</Link>\n                </DropdownMenuItem>\n              )\n          )}\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  )\n}\n"
  },
  {
    "path": "src/components/site-header.tsx",
    "content": "import Link from 'next/link'\n\nimport { siteConfig } from '@/config/site'\nimport { Icons } from '@/components/icons'\nimport { MainNav } from '@/components/main-nav'\nimport { buttonVariants } from '@/components/ui/button'\n\nexport function SiteHeader() {\n  return (\n    <header className=\"sticky top-0 z-40 w-full border-b border-b-slate-200 bg-white dark:border-b-slate-700 dark:bg-slate-900\">\n      <div className=\"container flex h-16 items-center space-x-4 sm:justify-between sm:space-x-0\">\n        <MainNav items={siteConfig.mainNav} />\n        <div className=\"flex flex-1 items-center justify-end space-x-4\">\n          <nav className=\"flex items-center space-x-1\">\n            <Link\n              href={siteConfig.links.github}\n              target=\"_blank\"\n              rel=\"noreferrer\"\n            >\n              <div\n                className={buttonVariants({\n                  size: 'sm',\n                  variant: 'ghost',\n                  className: 'text-slate-700 dark:text-slate-400',\n                })}\n              >\n                <Icons.gitHub className=\"h-5 w-5\" />\n                <span className=\"sr-only\">GitHub</span>\n              </div>\n            </Link>\n            <Link\n              href={siteConfig.links.youtube}\n              target=\"_blank\"\n              rel=\"noreferrer\"\n            >\n              <div\n                className={buttonVariants({\n                  size: 'sm',\n                  variant: 'ghost',\n                  className: '',\n                })}\n              >\n                <Icons.youtube className=\"h-6 w-6\" />\n                <span className=\"sr-only\">YouTube</span>\n              </div>\n            </Link>\n          </nav>\n        </div>\n      </div>\n    </header>\n  )\n}\n"
  },
  {
    "path": "src/components/ui/accordion.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as AccordionPrimitive from '@radix-ui/react-accordion'\nimport { ChevronDown } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nconst Accordion = AccordionPrimitive.Root\n\nconst AccordionItem = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n  <AccordionPrimitive.Item\n    ref={ref}\n    className={cn(\n      'border-b border-b-slate-200 dark:border-b-slate-700',\n      className\n    )}\n    {...props}\n  />\n))\nAccordionItem.displayName = 'AccordionItem'\n\nconst AccordionTrigger = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Header className=\"flex\">\n    <AccordionPrimitive.Trigger\n      ref={ref}\n      className={cn(\n        'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronDown className=\"h-4 w-4 transition-transform duration-200\" />\n    </AccordionPrimitive.Trigger>\n  </AccordionPrimitive.Header>\n))\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName\n\nconst AccordionContent = React.forwardRef<\n  React.ElementRef<typeof AccordionPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <AccordionPrimitive.Content\n    ref={ref}\n    className={cn(\n      'overflow-hidden text-sm transition-all data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up',\n      className\n    )}\n    {...props}\n  >\n    <div className=\"pt-0 pb-4\">{children}</div>\n  </AccordionPrimitive.Content>\n))\nAccordionContent.displayName = AccordionPrimitive.Content.displayName\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent }\n"
  },
  {
    "path": "src/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'\n\nconst AlertDialog = AlertDialogPrimitive.Root\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger\n\nconst AlertDialogPortal = ({\n  className,\n  children,\n  ...props\n}: AlertDialogPrimitive.AlertDialogPortalProps) => (\n  <AlertDialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className=\"fixed inset-0 z-50 flex items-end justify-center sm:items-center\">\n      {children}\n    </div>\n  </AlertDialogPrimitive.Portal>\n)\nAlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      'fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in',\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 z-50 grid w-full max-w-lg scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full',\n        'dark:bg-slate-900',\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(\n      'text-lg font-semibold text-slate-900',\n      'dark:text-slate-50',\n      className\n    )}\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-slate-500', 'dark:text-slate-400', 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(\n      'inline-flex h-10 items-center justify-center rounded-md bg-slate-900 py-2 px-4 text-sm font-semibold text-white transition-colors hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-slate-100 dark:text-slate-900 dark:hover:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',\n      className\n    )}\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      'mt-2 inline-flex h-10 items-center justify-center rounded-md border border-slate-200 bg-transparent py-2 px-4 text-sm font-semibold text-slate-900 transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 sm:mt-0',\n      className\n    )}\n    {...props}\n  />\n))\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName\n\nexport {\n  AlertDialog,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel,\n}\n"
  },
  {
    "path": "src/components/ui/aspect-ratio.tsx",
    "content": "'use client'\n\nimport * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'\n\nconst AspectRatio = AspectRatioPrimitive.Root\n\nexport { AspectRatio }\n"
  },
  {
    "path": "src/components/ui/avatar.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as AvatarPrimitive from '@radix-ui/react-avatar'\n\nimport { cn } from '@/lib/utils'\n\nconst Avatar = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Root\n    ref={ref}\n    className={cn(\n      'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',\n      className\n    )}\n    {...props}\n  />\n))\nAvatar.displayName = AvatarPrimitive.Root.displayName\n\nconst AvatarImage = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image\n    ref={ref}\n    className={cn('aspect-square h-full w-full', className)}\n    {...props}\n  />\n))\nAvatarImage.displayName = AvatarPrimitive.Image.displayName\n\nconst AvatarFallback = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn(\n      'flex h-full w-full items-center justify-center rounded-full bg-slate-100 dark:bg-slate-700',\n      className\n    )}\n    {...props}\n  />\n))\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName\n\nexport { Avatar, AvatarImage, AvatarFallback }\n"
  },
  {
    "path": "src/components/ui/button.tsx",
    "content": "import * as React from 'react'\nimport Link from 'next/link'\nimport { VariantProps, cva } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800',\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900',\n        destructive:\n          'bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600',\n        outline:\n          'bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100',\n        subtle:\n          'bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100',\n        ghost:\n          'bg-transparent dark:bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent',\n        link: 'bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-300 hover:bg-transparent dark:hover:bg-transparent',\n      },\n      size: {\n        default: 'h-10 py-2 px-4',\n        sm: 'h-9 px-2 rounded-md',\n        lg: 'h-11 px-8 rounded-md',\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  href?: string\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, children, href, variant, size, ...props }, ref) => {\n    if (href) {\n      return (\n        <Link\n          href={href}\n          className={cn(buttonVariants({ variant, size, className }))}\n        >\n          {children}\n        </Link>\n      )\n    }\n    return (\n      <button\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      >\n        {children}\n      </button>\n    )\n  }\n)\nButton.displayName = 'Button'\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "src/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-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',\n      className\n    )}\n    {...props}\n  >\n    <CheckboxPrimitive.Indicator\n      className={cn('flex items-center justify-center')}\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": "src/components/ui/collapsible.tsx",
    "content": "'use client'\n\nimport * as CollapsiblePrimitive from '@radix-ui/react-collapsible'\n\nconst Collapsible = CollapsiblePrimitive.Root\n\nconst CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger\n\nconst CollapsibleContent = CollapsiblePrimitive.CollapsibleContent\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent }\n"
  },
  {
    "path": "src/components/ui/context-menu.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as ContextMenuPrimitive from '@radix-ui/react-context-menu'\nimport { Check, ChevronRight, Circle } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nconst ContextMenu = ContextMenuPrimitive.Root\n\nconst ContextMenuTrigger = ContextMenuPrimitive.Trigger\n\nconst ContextMenuGroup = ContextMenuPrimitive.Group\n\nconst ContextMenuPortal = ContextMenuPrimitive.Portal\n\nconst ContextMenuSub = ContextMenuPrimitive.Sub\n\nconst ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup\n\nconst ContextMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <ContextMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      'flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\n  </ContextMenuPrimitive.SubTrigger>\n))\nContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName\n\nconst ContextMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      'z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 shadow-md animate-in slide-in-from-left-1 dark:border-slate-700 dark:bg-slate-800',\n      className\n    )}\n    {...props}\n  />\n))\nContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName\n\nconst ContextMenuContent = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.Portal>\n    <ContextMenuPrimitive.Content\n      ref={ref}\n      className={cn(\n        'z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 text-slate-700 shadow-md animate-in fade-in-80 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400',\n        className\n      )}\n      {...props}\n    />\n  </ContextMenuPrimitive.Portal>\n))\nContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName\n\nconst ContextMenuItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <ContextMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      'relative flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  />\n))\nContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName\n\nconst ContextMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <ContextMenuPrimitive.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 font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\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      <ContextMenuPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </ContextMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </ContextMenuPrimitive.CheckboxItem>\n))\nContextMenuCheckboxItem.displayName =\n  ContextMenuPrimitive.CheckboxItem.displayName\n\nconst ContextMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <ContextMenuPrimitive.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 font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <ContextMenuPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </ContextMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </ContextMenuPrimitive.RadioItem>\n))\nContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName\n\nconst ContextMenuLabel = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <ContextMenuPrimitive.Label\n    ref={ref}\n    className={cn(\n      'px-2 py-1.5 text-sm font-semibold text-slate-900 dark:text-slate-300',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  />\n))\nContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName\n\nconst ContextMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof ContextMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <ContextMenuPrimitive.Separator\n    ref={ref}\n    className={cn('-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-700', className)}\n    {...props}\n  />\n))\nContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName\n\nconst ContextMenuShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-xs tracking-widest text-slate-500',\n        className\n      )}\n      {...props}\n    />\n  )\n}\nContextMenuShortcut.displayName = 'ContextMenuShortcut'\n\nexport {\n  ContextMenu,\n  ContextMenuTrigger,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuCheckboxItem,\n  ContextMenuRadioItem,\n  ContextMenuLabel,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuGroup,\n  ContextMenuPortal,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuRadioGroup,\n}\n"
  },
  {
    "path": "src/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 = ({\n  className,\n  children,\n  ...props\n}: DialogPrimitive.DialogPortalProps) => (\n  <DialogPrimitive.Portal className={cn(className)} {...props}>\n    <div className=\"fixed inset-0 z-50 flex items-start justify-center sm:items-center\">\n      {children}\n    </div>\n  </DialogPrimitive.Portal>\n)\nDialogPortal.displayName = DialogPrimitive.Portal.displayName\n\nconst DialogOverlay = React.forwardRef<\n  React.ElementRef<typeof DialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <DialogPrimitive.Overlay\n    className={cn(\n      'fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in',\n      className\n    )}\n    {...props}\n    ref={ref}\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 z-50 grid w-full scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0',\n        'dark:bg-slate-900',\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <DialogPrimitive.Close className=\"absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800\">\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-2 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 text-slate-900',\n      'dark:text-slate-50',\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-slate-500', 'dark:text-slate-400', className)}\n    {...props}\n  />\n))\nDialogDescription.displayName = DialogPrimitive.Description.displayName\n\nexport {\n  Dialog,\n  DialogTrigger,\n  DialogContent,\n  DialogHeader,\n  DialogFooter,\n  DialogTitle,\n  DialogDescription,\n}\n"
  },
  {
    "path": "src/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 select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\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 border-slate-100 bg-white p-1 shadow-md animate-in slide-in-from-left-1 dark:border-slate-700 dark:bg-slate-800',\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 border-slate-100 bg-white p-1 text-slate-700 shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400',\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 rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\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 font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\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 font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\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 text-slate-900 dark:text-slate-300',\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-slate-100 dark:bg-slate-700', 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(\n        'ml-auto text-xs tracking-widest text-slate-500',\n        className\n      )}\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": "src/components/ui/file-input.tsx",
    "content": "'use client'\n\nimport {\n  forwardRef,\n  useReducer,\n  useState,\n  type ChangeEvent,\n  type DragEvent,\n} from 'react'\nimport { useS3Upload } from '@/src/hooks/use-s3-upload'\nimport { useToast } from '@/src/hooks/use-toast'\nimport ImageUpload from '@/ui/image-upload'\n\nimport { MAX_FILE_SIZE } from '@/config/image'\nimport { cn, validateFileType } from '@/lib/utils'\nimport { Icons } from '../icons'\n\ninterface FileWithUrl {\n  name: string\n  getUrl: string\n  size: number\n  error?: boolean | undefined\n}\n\n// Reducer action(s)\nconst addFilesToInput = () => ({\n  type: 'ADD_FILES_TO_INPUT' as const,\n  payload: [] as FileWithUrl[],\n})\n\ntype Action = ReturnType<typeof addFilesToInput>\ntype State = FileWithUrl[]\n\nexport interface InputProps\n  extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {}\n\nconst FileInput = forwardRef<HTMLInputElement, InputProps>(\n  ({ className, ...props }, ref) => {\n    const { toast } = useToast()\n    const { s3Upload } = useS3Upload()\n    const [dragActive, setDragActive] = useState<boolean>(false)\n    const [input, dispatch] = useReducer((state: State, action: Action) => {\n      switch (action.type) {\n        case 'ADD_FILES_TO_INPUT': {\n          // do not allow more than 5 files to be uploaded at once\n          if (state.length + action.payload.length > 10) {\n            toast({\n              title: 'Too many files',\n              description:\n                'You can only upload a maximum of 5 files at a time.',\n            })\n            return state\n          }\n\n          return [...state, ...action.payload]\n        }\n\n        // You could extend this, for example to allow removing files\n      }\n    }, [])\n\n    const noInput = input.length === 0\n\n    // handle drag events\n    const handleDrag = (e: DragEvent<HTMLFormElement | HTMLDivElement>) => {\n      e.preventDefault()\n      e.stopPropagation()\n      if (e.type === 'dragenter' || e.type === 'dragover') {\n        setDragActive(true)\n      } else if (e.type === 'dragleave') {\n        setDragActive(false)\n      }\n    }\n\n    // triggers when file is selected with click\n    const handleChange = async (e: ChangeEvent<HTMLInputElement>) => {\n      e.preventDefault()\n      try {\n        if (e.target.files && e.target.files[0]) {\n          // at least one file has been selected\n\n          // validate file type\n          const valid = validateFileType(e.target.files[0])\n          if (!valid) {\n            toast({\n              title: 'Invalid file type',\n              description: 'Please upload a valid file type.',\n            })\n            return\n          }\n\n          const { getUrl, error } = await s3Upload(e.target.files[0])\n          if (!getUrl || error) throw new Error('Error uploading file')\n\n          const { name, size } = e.target.files[0]\n\n          addFilesToState([{ name, getUrl, size }])\n        }\n      } catch (error) {\n        // already handled\n      }\n    }\n\n    const addFilesToState = (files: FileWithUrl[]) => {\n      dispatch({ type: 'ADD_FILES_TO_INPUT', payload: files })\n    }\n\n    // triggers when file is dropped\n    const handleDrop = async (e: DragEvent<HTMLDivElement>) => {\n      e.preventDefault()\n      e.stopPropagation()\n\n      // validate file type\n      if (e.dataTransfer.files && e.dataTransfer.files[0]) {\n        const files = Array.from(e.dataTransfer.files)\n        const validFiles = files.filter((file) => validateFileType(file))\n\n        if (files.length !== validFiles.length) {\n          toast({\n            title: 'Invalid file type',\n            description: 'Only image files are allowed.',\n          })\n        }\n\n        try {\n          const filesWithUrl = await Promise.all(\n            validFiles.map(async (file) => {\n              const { name, size } = file\n              const { getUrl, error } = await s3Upload(file)\n\n              if (!getUrl || error) return { name, size, getUrl: '', error }\n              return { name, size, getUrl }\n            })\n          )\n\n          setDragActive(false)\n\n          // at least one file has been selected\n          addFilesToState(filesWithUrl)\n\n          e.dataTransfer.clearData()\n        } catch (error) {\n          // already handled\n        }\n      }\n    }\n\n    return (\n      <form\n        onSubmit={(e) => e.preventDefault()}\n        onDragEnter={handleDrag}\n        className=\"flex h-full items-center w-full lg:w-2/3 justify-start\"\n      >\n        <label\n          htmlFor=\"dropzone-file\"\n          className={cn(\n            'group relative h-full flex flex-col items-center justify-center w-full aspect-video border-2 border-slate-300 border-dashed rounded-lg dark:border-gray-600 transition',\n            { 'dark:border-slate-400 dark:bg-slate-800': dragActive },\n            { 'h-fit aspect-auto': !noInput },\n            { 'items-start justify-start': !noInput },\n            { 'dark:hover:border-gray-500 dark:hover:bg-slate-800': noInput }\n          )}\n        >\n          <div\n            className={cn(\n              'relative w-full h-full flex flex-col items-center justify-center',\n              { 'items-start': !noInput }\n            )}\n          >\n            {noInput ? (\n              <>\n                <div\n                  className=\"absolute inset-0 cursor-pointer\"\n                  onDragEnter={handleDrag}\n                  onDragLeave={handleDrag}\n                  onDragOver={handleDrag}\n                  onDrop={handleDrop}\n                />\n\n                <svg\n                  aria-hidden=\"true\"\n                  className=\"w-10 h-10 mb-3 text-gray-400\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  viewBox=\"0 0 24 24\"\n                  xmlns=\"http://www.w3.org/2000/svg\"\n                >\n                  <path\n                    strokeLinecap=\"round\"\n                    strokeLinejoin=\"round\"\n                    strokeWidth=\"2\"\n                    d=\"M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12\"\n                  ></path>\n                </svg>\n\n                <p className=\"mb-2 text-sm text-gray-500 dark:text-gray-400\">\n                  <span className=\"font-semibold\">Click to upload</span> or drag\n                  and drop\n                </p>\n                <p className=\"text-xs text-gray-500 dark:text-gray-400\">\n                  up to 5 images, {(MAX_FILE_SIZE / 1000000).toFixed(0)}MB per\n                  file\n                </p>\n\n                <input\n                  {...props}\n                  ref={ref}\n                  multiple\n                  onChange={handleChange}\n                  accept=\"image/jpeg, image/jpg, image/png\"\n                  id=\"dropzone-file\"\n                  type=\"file\"\n                  className=\"hidden\"\n                />\n              </>\n            ) : (\n              <div className=\"flex flex-col w-full h-full\">\n                <div className=\"overflow-x-auto sm:-mx-6 lg:-mx-8\">\n                  <div className=\"align-middle inline-block min-w-full sm:px-6 lg:px-8\">\n                    <div className=\"shadow overflow-hidden sm:rounded-lg\">\n                      <table className=\"min-w-full divide-y dark:divide-slate-600\">\n                        <thead className=\"bg-slate-800\">\n                          <tr>\n                            <th\n                              scope=\"col\"\n                              className=\"px-6 py-3 text-left text-xs font-medium dark:text-slate-300  uppercase tracking-wider\"\n                            >\n                              Preview\n                            </th>\n                            <th\n                              scope=\"col\"\n                              className=\"px-6 py-3 text-left text-xs font-medium dark:text-slate-300  uppercase tracking-wider\"\n                            >\n                              Name\n                            </th>\n                            <th\n                              scope=\"col\"\n                              className=\"px-6 py-3 text-left text-xs font-medium dark:text-slate-300  uppercase tracking-wider\"\n                            >\n                              Size\n                            </th>\n                            <th\n                              scope=\"col\"\n                              className=\"px-6 py-3 text-left text-xs font-medium dark:text-slate-300  uppercase tracking-wider\"\n                            >\n                              Status\n                            </th>\n                          </tr>\n                        </thead>\n                        <tbody className=\"relative divide-y dark:divide-slate-600\">\n                          {input.map((file, index) => (\n                            <ImageUpload\n                              key={index}\n                              error={file.error}\n                              getUrl={file.getUrl}\n                              name={file.name}\n                              size={file.size}\n                            />\n                          ))}\n                        </tbody>\n                      </table>\n\n                      <label\n                        htmlFor=\"dropzone-file-images-present\"\n                        className=\"relative cursor-pointer group hover:border-gray-500 hover:dark:bg-slate-800 transition flex justify-center py-4 border-t border-slate-600\"\n                      >\n                        <Icons.plus className=\"group-hover:fill-slate-400 transition stroke-1 w-12 h-12 fill-slate-500\" />\n                        <input\n                          {...props}\n                          ref={ref}\n                          multiple\n                          onChange={handleChange}\n                          accept=\"image/jpeg, image/jpg, image/png\"\n                          type=\"file\"\n                          id=\"dropzone-file-images-present\"\n                          className=\"relative z-20 hidden\"\n                        />\n                        <div\n                          className=\"absolute inset-0\"\n                          onDragEnter={handleDrag}\n                          onDragLeave={handleDrag}\n                          onDragOver={handleDrag}\n                          onDrop={handleDrop}\n                        />\n                      </label>\n                    </div>\n                  </div>\n                </div>\n              </div>\n            )}\n          </div>\n        </label>\n      </form>\n    )\n  }\n)\nFileInput.displayName = 'FileInput'\n\nexport { FileInput }\n"
  },
  {
    "path": "src/components/ui/hover-card.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as HoverCardPrimitive from '@radix-ui/react-hover-card'\n\nimport { cn } from '@/lib/utils'\n\nconst HoverCard = HoverCardPrimitive.Root\n\nconst HoverCardTrigger = HoverCardPrimitive.Trigger\n\nconst HoverCardContent = React.forwardRef<\n  React.ElementRef<typeof HoverCardPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\n  <HoverCardPrimitive.Content\n    ref={ref}\n    align={align}\n    sideOffset={sideOffset}\n    className={cn(\n      'z-50 w-64 rounded-md border border-slate-100 bg-white p-4 shadow-md outline-none animate-in zoom-in-90 dark:border-slate-800 dark:bg-slate-800',\n      className\n    )}\n    {...props}\n  />\n))\nHoverCardContent.displayName = HoverCardPrimitive.Content.displayName\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n"
  },
  {
    "path": "src/components/ui/image-upload.tsx",
    "content": "'use client'\n\nimport { forwardRef } from 'react'\nimport Image from 'next/image'\nimport { useUploadFile } from '@/src/hooks/use-upload-file'\nimport { ImageResponseData } from '@/src/types/api/image'\nimport { Progress } from '@/ui/progress'\nimport { Loader2 } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\nimport { Icons } from '@/components/icons'\n\ninterface ImageUploadProps extends React.HTMLAttributes<HTMLTableRowElement> {\n  name: string\n  size: number\n  getUrl: string\n  error?: boolean | undefined\n}\n\nconst ImageUpload = forwardRef<HTMLTableRowElement, ImageUploadProps>(\n  ({ getUrl, error, name, size, className, ...props }, ref) => {\n    const {\n      data,\n      progress,\n      isLoading,\n      error: processingError,\n    } = useUploadFile<ImageResponseData>('/api/image/process', getUrl, {\n      disabled: error,\n    })\n\n    return (\n      <tr ref={ref} {...props} className={cn('', className)}>\n        <td className=\"px-6 py-4 whitespace-nowrap text-sm dark:text-slate-400\">\n          <div className=\"relative flex h-12 w-20\">\n            {error ? (\n              <div className=\"flex w-full justify-center items-center\">\n                <Icons.redx className=\"h-6 w-6\" />\n              </div>\n            ) : (\n              <Image\n                style={{ objectFit: 'contain' }}\n                src={getUrl}\n                fill\n                alt={name}\n              />\n            )}\n          </div>\n        </td>\n        <td className=\"px-6 py-4 truncate whitespace-normal text-sm font-medium dark:text-slate-400 \">\n          <div className=\"\">\n            <p\n              className={cn('dark:text-slate-300', {\n                'dark:text-red-500': error,\n              })}\n            >\n              {name}\n            </p>\n            {data ? (\n              <p>{data.alt}</p>\n            ) : isLoading ? (\n              <Loader2 className=\"mt-1 w-4 h-4 animate-spin\" />\n            ) : null}\n          </div>\n        </td>\n        <td\n          className={cn(\n            'px-6 py-4 whitespace-nowrap text-sm dark:text-slate-400',\n            {\n              'dark:text-red-500': error,\n            }\n          )}\n        >\n          {(size / 1000).toFixed(0)} KB\n        </td>\n        <td className=\"px-6 py-4 whitespace-nowrap text-sm dark:text-slate-400 \">\n          <Progress\n            className={cn('w-full h-2')}\n            value={progress}\n            isError={error || processingError}\n          />\n        </td>\n      </tr>\n    )\n  }\n)\n\nImageUpload.displayName = 'ImageUpload'\n\nexport default ImageUpload\n"
  },
  {
    "path": "src/components/ui/input.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <input\n        className={cn(\n          'flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nInput.displayName = 'Input'\n\nexport { Input }\n"
  },
  {
    "path": "src/components/ui/label.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as LabelPrimitive from '@radix-ui/react-label'\n\nimport { cn } from '@/lib/utils'\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(\n      'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',\n      className\n    )}\n    {...props}\n  />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n"
  },
  {
    "path": "src/components/ui/menubar.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as MenubarPrimitive from '@radix-ui/react-menubar'\nimport { Check, ChevronRight, Circle } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nconst MenubarMenu = MenubarPrimitive.Menu\n\nconst MenubarGroup = MenubarPrimitive.Group\n\nconst MenubarPortal = MenubarPrimitive.Portal\n\nconst MenubarSub = MenubarPrimitive.Sub\n\nconst MenubarRadioGroup = MenubarPrimitive.RadioGroup\n\nconst Menubar = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Root\n    ref={ref}\n    className={cn(\n      'flex h-10 items-center space-x-1 rounded-md border border-slate-300 bg-white p-1 dark:border-slate-700 dark:bg-slate-800',\n      className\n    )}\n    {...props}\n  />\n))\nMenubar.displayName = MenubarPrimitive.Root.displayName\n\nconst MenubarTrigger = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      'flex cursor-default select-none items-center rounded-[0.2rem] py-1.5 px-3 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700',\n      className\n    )}\n    {...props}\n  />\n))\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName\n\nconst MenubarSubTrigger = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {\n    inset?: boolean\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <MenubarPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      'flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRight className=\"ml-auto h-4 w-4\" />\n  </MenubarPrimitive.SubTrigger>\n))\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName\n\nconst MenubarSubContent = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      'z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 shadow-md animate-in slide-in-from-left-1 dark:border-slate-700 dark:bg-slate-800',\n      className\n    )}\n    {...props}\n  />\n))\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName\n\nconst MenubarContent = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>\n>(\n  (\n    { className, align = 'start', alignOffset = -4, sideOffset = 8, ...props },\n    ref\n  ) => (\n    <MenubarPrimitive.Portal>\n      <MenubarPrimitive.Content\n        ref={ref}\n        align={align}\n        alignOffset={alignOffset}\n        sideOffset={sideOffset}\n        className={cn(\n          'z-50 min-w-[12rem] overflow-hidden rounded-md border border-slate-100 bg-white p-1 text-slate-700 shadow-md animate-in slide-in-from-top-1 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400',\n          className\n        )}\n        {...props}\n      />\n    </MenubarPrimitive.Portal>\n  )\n)\nMenubarContent.displayName = MenubarPrimitive.Content.displayName\n\nconst MenubarItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <MenubarPrimitive.Item\n    ref={ref}\n    className={cn(\n      'relative flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  />\n))\nMenubarItem.displayName = MenubarPrimitive.Item.displayName\n\nconst MenubarCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <MenubarPrimitive.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 font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\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      <MenubarPrimitive.ItemIndicator>\n        <Check className=\"h-4 w-4\" />\n      </MenubarPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </MenubarPrimitive.CheckboxItem>\n))\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName\n\nconst MenubarRadioItem = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <MenubarPrimitive.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 font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\n      className\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <MenubarPrimitive.ItemIndicator>\n        <Circle className=\"h-2 w-2 fill-current\" />\n      </MenubarPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </MenubarPrimitive.RadioItem>\n))\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName\n\nconst MenubarLabel = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {\n    inset?: boolean\n  }\n>(({ className, inset, ...props }, ref) => (\n  <MenubarPrimitive.Label\n    ref={ref}\n    className={cn(\n      'px-2 py-1.5 text-sm font-semibold text-slate-900 dark:text-slate-300',\n      inset && 'pl-8',\n      className\n    )}\n    {...props}\n  />\n))\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName\n\nconst MenubarSeparator = React.forwardRef<\n  React.ElementRef<typeof MenubarPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <MenubarPrimitive.Separator\n    ref={ref}\n    className={cn('-mx-1 my-1 h-px bg-slate-100 dark:bg-slate-700', className)}\n    {...props}\n  />\n))\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName\n\nconst MenubarShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\n        'ml-auto text-xs tracking-widest text-slate-500',\n        className\n      )}\n      {...props}\n    />\n  )\n}\nMenubarShortcut.displayname = 'MenubarShortcut'\n\nexport {\n  Menubar,\n  MenubarMenu,\n  MenubarTrigger,\n  MenubarContent,\n  MenubarItem,\n  MenubarSeparator,\n  MenubarLabel,\n  MenubarCheckboxItem,\n  MenubarRadioGroup,\n  MenubarRadioItem,\n  MenubarPortal,\n  MenubarSubContent,\n  MenubarSubTrigger,\n  MenubarGroup,\n  MenubarSub,\n  MenubarShortcut,\n}\n"
  },
  {
    "path": "src/components/ui/navigation-menu.tsx",
    "content": "import * as React from 'react'\nimport * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'\nimport { cva } from 'class-variance-authority'\nimport { ChevronDown } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nconst NavigationMenu = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Root\n    ref={ref}\n    className={cn(\n      'relative z-10 flex flex-1 items-center justify-center',\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <NavigationMenuViewport />\n  </NavigationMenuPrimitive.Root>\n))\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName\n\nconst NavigationMenuList = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.List\n    ref={ref}\n    className={cn(\n      'group flex flex-1 list-none items-center justify-center',\n      className\n    )}\n    {...props}\n  />\n))\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item\n\nconst navigationMenuTriggerStyle = cva(\n  'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:bg-slate-100 disabled:opacity-50 dark:focus:bg-slate-800 disabled:pointer-events-none bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-slate-50 dark:data-[state=open]:bg-slate-800 h-10 py-2 px-4 group'\n)\n\nconst NavigationMenuTrigger = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <NavigationMenuPrimitive.Trigger\n    ref={ref}\n    className={cn(navigationMenuTriggerStyle(), 'group', className)}\n    {...props}\n  >\n    {children}{' '}\n    <ChevronDown\n      className=\"relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180\"\n      aria-hidden=\"true\"\n    />\n  </NavigationMenuPrimitive.Trigger>\n))\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName\n\nconst NavigationMenuContent = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Content\n    ref={ref}\n    className={cn(\n      'absolute top-0 left-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=to-start]:slide-out-to-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=from-end]:slide-in-from-right-52 md:w-auto',\n      className\n    )}\n    {...props}\n  />\n))\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link\n\nconst NavigationMenuViewport = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n  <div className={cn('absolute left-0 top-full flex justify-center')}>\n    <NavigationMenuPrimitive.Viewport\n      className={cn(\n        'origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border border-slate-200 bg-white shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:zoom-in-90 data-[state=closed]:zoom-out-95 dark:border-slate-700 dark:bg-slate-800 md:w-[var(--radix-navigation-menu-viewport-width)]',\n        className\n      )}\n      ref={ref}\n      {...props}\n    />\n  </div>\n))\nNavigationMenuViewport.displayName =\n  NavigationMenuPrimitive.Viewport.displayName\n\nconst NavigationMenuIndicator = React.forwardRef<\n  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,\n  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n  <NavigationMenuPrimitive.Indicator\n    ref={ref}\n    className={cn(\n      'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=visible]:fade-in data-[state=hidden]:fade-out',\n      className\n    )}\n    {...props}\n  >\n    <div className=\"relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-slate-200 shadow-md dark:bg-slate-800\" />\n  </NavigationMenuPrimitive.Indicator>\n))\nNavigationMenuIndicator.displayName =\n  NavigationMenuPrimitive.Indicator.displayName\n\nexport {\n  navigationMenuTriggerStyle,\n  NavigationMenu,\n  NavigationMenuList,\n  NavigationMenuItem,\n  NavigationMenuContent,\n  NavigationMenuTrigger,\n  NavigationMenuLink,\n  NavigationMenuIndicator,\n  NavigationMenuViewport,\n}\n"
  },
  {
    "path": "src/components/ui/popover.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\n\nimport { cn } from '@/lib/utils'\n\nconst Popover = PopoverPrimitive.Root\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverContent = React.forwardRef<\n  React.ElementRef<typeof PopoverPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\n  <PopoverPrimitive.Portal>\n    <PopoverPrimitive.Content\n      ref={ref}\n      align={align}\n      sideOffset={sideOffset}\n      className={cn(\n        'z-50 w-72 rounded-md border border-slate-100 bg-white p-4 shadow-md outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-800',\n        className\n      )}\n      {...props}\n    />\n  </PopoverPrimitive.Portal>\n))\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport { Popover, PopoverTrigger, PopoverContent }\n"
  },
  {
    "path": "src/components/ui/progress.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as ProgressPrimitive from '@radix-ui/react-progress'\nimport { cva } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\ninterface ProgressProps\n  extends React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> {\n  isSuccess?: boolean\n  isError?: boolean\n}\n\nconst indicatorVariants = cva('h-full w-full flex-1 transition-all', {\n  variants: {\n    variant: {\n      default: 'bg-slate-900 dark:bg-slate-400',\n      isError: 'bg-red-500 dark:bg-red-500',\n      isSuccess: 'bg-green-500 dark:bg-green-400',\n    },\n  },\n})\n\nconst rootVariants = cva('relative h-4 w-full overflow-hidden rounded-full', {\n  variants: {\n    variant: {\n      default: 'bg-slate-200 dark:bg-slate-800',\n      isError: 'bg-red-200 dark:bg-red-800',\n      isSuccess: 'bg-green-200 dark:bg-green-800',\n    },\n  },\n})\n\nconst Progress = React.forwardRef<\n  React.ElementRef<typeof ProgressPrimitive.Root>,\n  ProgressProps\n>(({ className, value, isError, isSuccess, ...props }, ref) => {\n  const variant = isError ? 'isError' : 'default'\n\n  return (\n    <ProgressPrimitive.Root\n      ref={ref}\n      className={cn(rootVariants({ variant, className }))}\n      {...props}\n    >\n      <ProgressPrimitive.Indicator\n        className={cn(indicatorVariants({ variant }))}\n        style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n      />\n    </ProgressPrimitive.Root>\n  )\n})\nProgress.displayName = ProgressPrimitive.Root.displayName\n\nexport { Progress }\n"
  },
  {
    "path": "src/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, children, ...props }, ref) => {\n  return (\n    <RadioGroupPrimitive.Item\n      ref={ref}\n      className={cn(\n        'text:fill-slate-50 h-4 w-4 rounded-full border border-slate-300 text-slate-900 hover:border-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-100 dark:hover:text-slate-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',\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-slate-900 dark:fill-slate-50\" />\n      </RadioGroupPrimitive.Indicator>\n    </RadioGroupPrimitive.Item>\n  )\n})\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName\n\nexport { RadioGroup, RadioGroupItem }\n"
  },
  {
    "path": "src/components/ui/scroll-area.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'\n\nimport { cn } from '@/lib/utils'\n\nconst ScrollArea = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n  <ScrollAreaPrimitive.Root\n    ref={ref}\n    className={cn('relative overflow-hidden', className)}\n    {...props}\n  >\n    <ScrollAreaPrimitive.Viewport className=\"h-full w-full rounded-[inherit]\">\n      {children}\n    </ScrollAreaPrimitive.Viewport>\n    <ScrollBar />\n    <ScrollAreaPrimitive.Corner />\n  </ScrollAreaPrimitive.Root>\n))\nScrollArea.displayName = ScrollAreaPrimitive.Root.displayName\n\nconst ScrollBar = React.forwardRef<\n  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,\n  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>\n>(({ className, orientation = 'vertical', ...props }, ref) => (\n  <ScrollAreaPrimitive.ScrollAreaScrollbar\n    ref={ref}\n    orientation={orientation}\n    className={cn(\n      'flex touch-none select-none transition-colors',\n      orientation === 'vertical' &&\n        'h-full w-2.5 border-l border-l-transparent p-[1px]',\n      orientation === 'horizontal' &&\n        'h-2.5 border-t border-t-transparent p-[1px]',\n      className\n    )}\n    {...props}\n  >\n    <ScrollAreaPrimitive.ScrollAreaThumb className=\"relative flex-1 rounded-full bg-slate-300 dark:bg-slate-700\" />\n  </ScrollAreaPrimitive.ScrollAreaScrollbar>\n))\nScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName\n\nexport { ScrollArea, ScrollBar }\n"
  },
  {
    "path": "src/components/ui/select.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as SelectPrimitive from '@radix-ui/react-select'\nimport { Check, ChevronDown } 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-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',\n      className\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronDown className=\"h-4 w-4 opacity-50\" />\n  </SelectPrimitive.Trigger>\n))\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        'relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white text-slate-700 shadow-md animate-in fade-in-80 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400',\n        className\n      )}\n      {...props}\n    >\n      <SelectPrimitive.Viewport className=\"p-1\">\n        {children}\n      </SelectPrimitive.Viewport>\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(\n      'py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-slate-300',\n      className\n    )}\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 cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700',\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-slate-100 dark:bg-slate-700', 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}\n"
  },
  {
    "path": "src/components/ui/separator.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as SeparatorPrimitive from '@radix-ui/react-separator'\n\nimport { cn } from '@/lib/utils'\n\nconst Separator = React.forwardRef<\n  React.ElementRef<typeof SeparatorPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>\n>(\n  (\n    { className, orientation = 'horizontal', decorative = true, ...props },\n    ref\n  ) => (\n    <SeparatorPrimitive.Root\n      ref={ref}\n      decorative={decorative}\n      orientation={orientation}\n      className={cn(\n        'bg-slate-200 dark:bg-slate-700',\n        orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',\n        className\n      )}\n      {...props}\n    />\n  )\n)\nSeparator.displayName = SeparatorPrimitive.Root.displayName\n\nexport { Separator }\n"
  },
  {
    "path": "src/components/ui/slider.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as SliderPrimitive from '@radix-ui/react-slider'\n\nimport { cn } from '@/lib/utils'\n\nconst Slider = React.forwardRef<\n  React.ElementRef<typeof SliderPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, value, ...props }, ref) => (\n  <SliderPrimitive.Root\n    ref={ref}\n    className={cn(\n      'relative flex w-full touch-none select-none items-center',\n      className\n    )}\n    {...props}\n  >\n    <SliderPrimitive.Track className=\"relative h-2 w-full grow overflow-hidden rounded-full bg-slate-200 dark:bg-slate-800\">\n      <SliderPrimitive.Range className=\"absolute h-full bg-slate-900  dark:bg-slate-400\" />\n    </SliderPrimitive.Track>\n    <SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-slate-900 bg-white transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:border-slate-100 dark:bg-slate-400 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900\" />\n  </SliderPrimitive.Root>\n))\nSlider.displayName = SliderPrimitive.Root.displayName\n\nexport { Slider }\n"
  },
  {
    "path": "src/components/ui/spinner.tsx",
    "content": "import type { FC, HTMLAttributes } from 'react'\nimport { Loader2 } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\ninterface SpinnerProps extends HTMLAttributes<HTMLDivElement> {}\n\nconst Spinner: FC<SpinnerProps> = ({ className, ...props }) => {\n  return (\n    <Loader2\n      {...props}\n      className={cn('mr-2 h-4 w-4 animate-spin', className)}\n    />\n  )\n}\n\nexport default Spinner\n"
  },
  {
    "path": "src/components/ui/switch.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as SwitchPrimitives from '@radix-ui/react-switch'\n\nimport { cn } from '@/lib/utils'\n\nconst Switch = React.forwardRef<\n  React.ElementRef<typeof SwitchPrimitives.Root>,\n  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n  <SwitchPrimitives.Root\n    className={cn(\n      'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=unchecked]:bg-slate-200 data-[state=checked]:bg-slate-900 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=unchecked]:bg-slate-700 dark:data-[state=checked]:bg-slate-400',\n      className\n    )}\n    {...props}\n    ref={ref}\n  >\n    <SwitchPrimitives.Thumb\n      className={cn(\n        'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=unchecked]:translate-x-0 data-[state=checked]:translate-x-5'\n      )}\n    />\n  </SwitchPrimitives.Root>\n))\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\nexport { Switch }\n"
  },
  {
    "path": "src/components/ui/tabs.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\n\nimport { cn } from '@/lib/utils'\n\nconst Tabs = TabsPrimitive.Root\n\nconst TabsList = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.List\n    ref={ref}\n    className={cn(\n      'inline-flex items-center justify-center rounded-md bg-slate-100 p-1 dark:bg-slate-800',\n      className\n    )}\n    {...props}\n  />\n))\nTabsList.displayName = TabsPrimitive.List.displayName\n\nconst TabsTrigger = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Trigger\n    className={cn(\n      'inline-flex min-w-[100px] items-center justify-center rounded-[0.185rem] px-3 py-1.5  text-sm font-medium text-slate-700 transition-all  disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm dark:text-slate-200 dark:data-[state=active]:bg-slate-900',\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName\n\nconst TabsContent = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Content\n    className={cn(\n      'mt-2 rounded-md border border-slate-200 p-6 dark:border-slate-700',\n      className\n    )}\n    {...props}\n    ref={ref}\n  />\n))\nTabsContent.displayName = TabsPrimitive.Content.displayName\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent }\n"
  },
  {
    "path": "src/components/ui/textarea.tsx",
    "content": "import * as React from 'react'\n\nimport { cn } from '@/lib/utils'\n\nexport interface TextareaProps\n  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n  ({ className, ...props }, ref) => {\n    return (\n      <textarea\n        className={cn(\n          'flex h-20 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900',\n          className\n        )}\n        ref={ref}\n        {...props}\n      />\n    )\n  }\n)\nTextarea.displayName = 'Textarea'\n\nexport { Textarea }\n"
  },
  {
    "path": "src/components/ui/toast.tsx",
    "content": "import * as React from 'react'\nimport * as ToastPrimitives from '@radix-ui/react-toast'\nimport { VariantProps, cva } 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:top-auto sm:bottom-0 sm:right-0 sm:flex-col md:max-w-[420px]',\n      className\n    )}\n    {...props}\n  />\n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n  'data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto 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=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4',\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700',\n        destructive:\n          'group destructive bg-red-600 text-white border-red-600 dark:border-red-600',\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 border-slate-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-red-100 group-[.destructive]:hover:border-slate-50 group-[.destructive]:hover:bg-red-100 group-[.destructive]:hover:text-red-600 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:hover:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800',\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 top-2 right-2 rounded-md p-1 text-slate-500 opacity-0 transition-opacity hover:text-slate-900 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 dark:hover:text-slate-50',\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": "src/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 '@/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": "src/components/ui/tooltip.tsx",
    "content": "'use client'\n\nimport * as React from 'react'\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip'\n\nimport { cn } from '@/lib/utils'\n\nconst TooltipProvider = TooltipPrimitive.Provider\n\nconst Tooltip = ({ ...props }) => <TooltipPrimitive.Root {...props} />\nTooltip.displayName = TooltipPrimitive.Tooltip.displayName\n\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\nconst TooltipContent = React.forwardRef<\n  React.ElementRef<typeof TooltipPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <TooltipPrimitive.Content\n    ref={ref}\n    sideOffset={sideOffset}\n    className={cn(\n      'z-50 overflow-hidden rounded-md border border-slate-100 bg-white px-3 py-1.5 text-sm text-slate-700 shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400',\n      className\n    )}\n    {...props}\n  />\n))\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }\n"
  },
  {
    "path": "src/config/image.ts",
    "content": "export const MAX_FILE_SIZE = 5000000 // 5MB\n"
  },
  {
    "path": "src/config/s3.ts",
    "content": "export const ALLOWED_FILE_TYPES = ['image/png', 'image/jpeg']\nexport const S3_BUCKET_NAME = 'image-to-alt'\n"
  },
  {
    "path": "src/config/site.ts",
    "content": "import { NavItem } from '@/src/types/nav'\n\ninterface SiteConfig {\n  name: string\n  description: string\n  mainNav: NavItem[]\n  links: {\n    youtube: string\n    github: string\n  }\n}\n\nexport const siteConfig: SiteConfig = {\n  name: 'ImageToAlt',\n  description: 'Easily create alt-descriptions for your images.',\n  mainNav: [\n    {\n      title: 'Home',\n      href: '/',\n    },\n  ],\n  links: {\n    github: 'https://github.com/joschan21/image-alt-generator',\n    youtube: 'https://www.youtube.com/@joshtriedcoding',\n  },\n}\n"
  },
  {
    "path": "src/hooks/use-s3-upload.ts",
    "content": "import { toast } from '@/hooks/use-toast'\n\nimport { MAX_FILE_SIZE } from '@/config/image'\nimport { FileTooLargeError } from '@/lib/exceptions'\nimport { s3ResponseSchema } from '@/lib/validations/s3'\n\ninterface UseS3UploadReturn {\n  s3Upload: (file: File) => Promise<{ getUrl: string | null; error: boolean }>\n}\n\nconst uploadFile = async (file: File) => {\n  try {\n    const res = await fetch('/api/image/presign', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        fileType: file.type,\n      }),\n    })\n\n    const data = await res.json()\n\n    const { fields, getUrl, postUrl } = s3ResponseSchema.parse(data)\n\n    const outboundToS3 = {\n      ...fields,\n      'Content-Type': file.type,\n      file,\n    }\n\n    const formData = new FormData()\n\n    Object.entries(outboundToS3).forEach(([key, value]) => {\n      formData.append(key, value)\n    })\n\n    try {\n      // Upload to S3\n      await fetch(postUrl, {\n        method: 'POST',\n        body: formData,\n      })\n    } catch (error) {\n      throw new FileTooLargeError()\n    }\n\n    return { getUrl }\n  } catch (error) {\n    if (error instanceof FileTooLargeError) {\n      throw new FileTooLargeError()\n    }\n\n    throw new Error('Internal Server Error')\n  }\n}\n\nexport const useS3Upload = (): UseS3UploadReturn => {\n  const s3Upload = async (file: File) => {\n    try {\n      if (file.size > MAX_FILE_SIZE) throw new FileTooLargeError()\n\n      // Single file upload\n      const singleFile = file as File\n\n      const { getUrl } = await uploadFile(singleFile)\n\n      return { getUrl, error: false }\n    } catch (error) {\n      if (error instanceof FileTooLargeError) {\n        toast({\n          title: 'Image Too Large',\n          description: error.message,\n        })\n\n        return { getUrl: null, error: true }\n      }\n\n      toast({\n        title: 'Internal Server Error',\n        description: 'There was an error uploading your image.',\n      })\n\n      return { getUrl: null, error: true }\n    }\n  }\n\n  return { s3Upload }\n}\n"
  },
  {
    "path": "src/hooks/use-toast.ts",
    "content": "// Inspired by react-hot-toast library\nimport * as React from 'react'\nimport { ToastActionElement, type ToastProps } from '@/ui/toast'\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000\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_VALUE\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    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\ninterface Toast extends 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": "src/hooks/use-upload-file.ts",
    "content": "import { useEffect, useReducer, useRef } from 'react'\nimport axios from 'axios'\n\nimport { useToast } from './use-toast'\n\ninterface State<T> {\n  data?: T\n  isLoading: boolean\n  progress?: number\n  error?: boolean\n}\n\n// discriminated union type\ntype Action<T> =\n  | { type: 'loading' }\n  | { type: 'fetched'; payload: T }\n  | { type: 'error'; payload: boolean }\n  | { type: 'progress'; payload: number }\n\ntype Options = {\n  disabled: boolean | undefined\n}\n\nexport const useUploadFile = <T = unknown>(\n  url: string,\n  resourceUrl: string,\n  options: Options\n) => {\n  const { toast } = useToast()\n  const { disabled } = options\n\n  // Used to prevent state update if the component is unmounted\n  const cancelRequest = useRef<boolean>(false)\n\n  const initialState: State<T> = {\n    error: undefined,\n    isLoading: false,\n    progress: undefined,\n    data: undefined,\n  }\n\n  // Keep state logic separated\n  const fetchReducer = (state: State<T>, action: Action<T>): State<T> => {\n    switch (action.type) {\n      case 'loading':\n        return { ...state, isLoading: true }\n      case 'fetched':\n        return { ...state, data: action.payload, isLoading: false }\n      case 'error':\n        return { ...state, error: action.payload, isLoading: false }\n      case 'progress':\n        return { ...state, progress: action.payload }\n      default:\n        return state\n    }\n  }\n\n  const [state, dispatch] = useReducer(fetchReducer, initialState)\n\n  useEffect(() => {\n    // Do nothing if the url is not given\n    if (!url || disabled) return\n\n    cancelRequest.current = false\n\n    const fetchData = async () => {\n      dispatch({ type: 'loading' })\n\n      try {\n        const res = await axios.post(url, resourceUrl, {\n          headers: {\n            'Content-Type': 'application/json',\n          },\n\n          onUploadProgress: (progressEvent) => {\n            const percentCompleted = Math.round(\n              (progressEvent.loaded * 100) / progressEvent.total!\n            )\n\n            dispatch({ type: 'progress', payload: percentCompleted })\n          },\n        })\n\n        const data = res.data as T\n        if (cancelRequest.current) return\n\n        dispatch({ type: 'fetched', payload: data })\n      } catch (error) {\n        if (cancelRequest.current) return\n\n        dispatch({ type: 'error', payload: true })\n\n        toast({\n          title: 'Something went wrong.',\n          description: 'Please try again later.',\n        })\n      }\n    }\n\n    void fetchData()\n\n    // Use the cleanup function for avoiding a possible...\n    // ...state update after the component was unmounted\n    return () => {\n      cancelRequest.current = true\n    }\n  }, [url])\n\n  return state\n}\n"
  },
  {
    "path": "src/lib/api-middlewares/with-methods.ts",
    "content": "import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'\n\nexport function withMethods(methods: string[], handler: NextApiHandler) {\n  return async function (req: NextApiRequest, res: NextApiResponse) {\n    if (!req.method || !methods.includes(req.method)) {\n      return res.status(405).end()\n    }\n\n    return handler(req, res)\n  }\n}\n"
  },
  {
    "path": "src/lib/exceptions.ts",
    "content": "import { MAX_FILE_SIZE } from '../config/image'\n\nexport class FileTooLargeError extends Error {\n  constructor(\n    message = `Images cannot be larger than ${MAX_FILE_SIZE / 1000000}MB.`\n  ) {\n    super(message)\n  }\n}\n"
  },
  {
    "path": "src/lib/s3.ts",
    "content": "import S3 from 'aws-sdk/clients/s3'\n\nexport const s3 = new S3({\n  apiVersion: '2006-03-01',\n  accessKeyId: process.env.S3_ACCESS_KEY_ID,\n  secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,\n  region: process.env.S3_REGION,\n  signatureVersion: 'v4',\n})\n"
  },
  {
    "path": "src/lib/utils.ts",
    "content": "import { ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nimport { ALLOWED_FILE_TYPES } from '../config/s3'\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n\nexport function validateFileType(file: File) {\n  return ALLOWED_FILE_TYPES.includes(file.type)\n}\n"
  },
  {
    "path": "src/lib/validations/s3.ts",
    "content": "import { ALLOWED_FILE_TYPES } from '@/src/config/s3'\nimport { z } from 'zod'\n\nexport const fileTypeSchema = z.string().refine(\n  (value) => {\n    return ALLOWED_FILE_TYPES.includes(value)\n  },\n  {\n    message: 'Invalid file type',\n  }\n)\n\n/**\n * fields: provided by AWS to authenticate request\n * key: file name in s3 bucket\n * getUrl: url to view the file from s3 bucket\n * postUrl: url that allows post request to s3 bucket\n */\n\nexport const s3ResponseSchema = z.object({\n  getUrl: z.string(),\n  postUrl: z.string(),\n  fields: z.object({\n    Policy: z.string(),\n    'X-Amz-Algorithm': z.string(),\n    'X-Amz-Credential': z.string(),\n    'X-Amz-Date': z.string(),\n    'X-Amz-Signature': z.string(),\n    bucket: z.string(),\n    key: z.string(),\n  }),\n})\n"
  },
  {
    "path": "src/pages/api/image/presign.ts",
    "content": "import { NextApiRequest, NextApiResponse } from 'next'\nimport { MAX_FILE_SIZE } from '@/src/config/image'\nimport { S3_BUCKET_NAME } from '@/src/config/s3'\nimport { withMethods } from '@/src/lib/api-middlewares/with-methods'\nimport { s3 } from '@/src/lib/s3'\nimport { fileTypeSchema } from '@/src/lib/validations/s3'\nimport { PresignResponseData } from '@/src/types/api/image'\nimport { nanoid } from 'nanoid'\nimport { z } from 'zod'\n\nconst handler = async (\n  req: NextApiRequest,\n  res: NextApiResponse<PresignResponseData>\n) => {\n  const reqFileType = req.body.fileType\n  const fileId = nanoid()\n\n  try {\n    // validate file extension, will throw if invalid\n    const fileType = fileTypeSchema.parse(reqFileType)\n    const fileExtension = fileType.split('/')[1]\n    const key = `${fileId}.${fileExtension}`\n\n    // Create a presigned POST request to upload the file to S3\n    const { url: postUrl, fields } = (await new Promise((resolve, reject) => {\n      s3.createPresignedPost(\n        {\n          Bucket: S3_BUCKET_NAME,\n          Fields: { key },\n          Expires: 60,\n          Conditions: [\n            ['content-length-range', 0, MAX_FILE_SIZE],\n            ['starts-with', '$Content-Type', 'image/'],\n          ],\n        },\n        (err, signed) => {\n          if (err) return reject(err)\n          resolve(signed)\n        }\n      )\n    })) as { url: string; fields: any }\n\n    const getUrl = await s3.getSignedUrlPromise('getObject', {\n      Bucket: S3_BUCKET_NAME,\n      Key: key,\n    })\n\n    return res.status(200).json({ postUrl, getUrl, fields })\n  } catch (error) {\n    if (error instanceof z.ZodError) {\n      return res.status(415).json(error.issues)\n    }\n\n    if (error instanceof Error) {\n      return res.status(500).json({ error: error.message })\n    }\n\n    return res.status(500).json({ error: 'Something went wrong' })\n  }\n}\n\nexport default withMethods(['POST'], handler)\n"
  },
  {
    "path": "src/pages/api/image/process.ts",
    "content": "import { NextApiRequest, NextApiResponse } from 'next'\nimport { ImageResponseData } from '@/src/types/api/image'\n\nimport { withMethods } from '@/lib/api-middlewares/with-methods'\n\ntype PartialReplicateResponse = {\n  urls: {\n    get: string\n    cancel: string\n  }\n}\n\nexport const config = {\n  api: {\n    bodyParser: {\n      sizeLimit: '4mb',\n    },\n  },\n}\n\nasync function handler(\n  req: NextApiRequest,\n  res: NextApiResponse<ImageResponseData>\n) {\n  if (!req.body)\n    return res\n      .status(400)\n      .json({ success: false, alt: '', message: 'No image data' })\n  const imageBase64 = req.body\n\n  try {\n    const startResponse = await fetch(\n      'https://api.replicate.com/v1/predictions',\n      {\n        method: 'POST',\n        headers: {\n          Authorization: `Token ${process.env.REPLICATE_API_KEY}`,\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          // Pinned to a specific version of Stable Diffusion\n          // See https://replicate.com/stability-ai/stable-diffussion/versions\n          version:\n            '9a34a6339872a03f45236f114321fb51fc7aa8269d38ae0ce5334969981e4cd8',\n\n          input: {\n            model: 'conceptual-captions',\n            use_beam_search: false,\n            image: imageBase64,\n          },\n        }),\n      }\n    )\n\n    let jsonStartResponse =\n      (await startResponse.json()) as PartialReplicateResponse\n    let endpointUrl = jsonStartResponse.urls.get\n\n    // GET request to get the status of alt text generation process & return the result when it's ready\n    let altText: string | null = null\n    while (!altText) {\n      // Poll in 1s intervals until the alt text is ready\n\n      let finalResponse = await fetch(endpointUrl, {\n        method: 'GET',\n        headers: {\n          'Content-Type': 'application/json',\n          Authorization: 'Token ' + process.env.REPLICATE_API_KEY,\n        },\n      })\n      let jsonFinalResponse = await finalResponse.json()\n\n      if (jsonFinalResponse.status === 'succeeded') {\n        altText = jsonFinalResponse.output as string\n\n        res.status(200).json({\n          success: true,\n          alt: altText,\n          message: 'Alt text generated successfully',\n        })\n\n        break\n      } else if (jsonFinalResponse.status === 'failed') {\n        res.status(503).json({\n          success: false,\n          alt: '',\n          message: 'Image could not be processed',\n        })\n        break\n      } else {\n        await new Promise((resolve) => setTimeout(resolve, 1000))\n      }\n    }\n  } catch (error) {\n    res\n      .status(500)\n      .json({ success: false, alt: '', message: 'Internal server error' })\n  }\n}\n\nexport default withMethods(['POST'], handler)\n"
  },
  {
    "path": "src/styles/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
  },
  {
    "path": "src/types/api/image.ts",
    "content": "import { s3ResponseSchema } from '@/src/lib/validations/s3'\nimport { ZodIssue, z } from 'zod'\n\nexport type ImageResponseData = {\n  success: boolean\n  alt: string\n  message: string\n}\n\nexport type PresignResponseData =\n  | z.infer<typeof s3ResponseSchema>\n  | ZodIssue[]\n  | { error: string }\n"
  },
  {
    "path": "src/types/nav.ts",
    "content": "export interface NavItem {\n  title: string\n  href?: string\n  disabled?: boolean\n  external?: boolean\n}\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "const { fontFamily } = require(\"tailwindcss/defaultTheme\")\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  darkMode: [\"class\"],\n  content: [\n    \"./app/**/*.{js,ts,jsx,tsx}\",\n    \"./pages/**/*.{js,ts,jsx,tsx}\",\n    \"./components/**/*.{js,ts,jsx,tsx}\",\n\n    // Or if using `src` directory:\n    \"./src/**/*.{js,ts,jsx,tsx}\",\n  ],\n  theme: {\n    container: {\n      center: true,\n      padding: \"1.5rem\",\n      screens: {\n        \"2xl\": \"1360px\",\n      },\n    },\n    extend: {\n      fontFamily: {\n        sans: [\"var(--font-inter)\", ...fontFamily.sans],\n      },\n      keyframes: {\n        \"accordion-down\": {\n          from: { height: 0 },\n          to: { height: \"var(--radix-accordion-content-height)\" },\n        },\n        \"accordion-up\": {\n          from: { height: \"var(--radix-accordion-content-height)\" },\n          to: { height: 0 },\n        },\n      },\n      animation: {\n        \"accordion-down\": \"accordion-down 0.2s ease-out\",\n        \"accordion-up\": \"accordion-up 0.2s ease-out\",\n      },\n    },\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"incremental\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./*\"],\n      \"@/ui/*\": [\"./src/components/ui/*\"],\n      \"@/hooks/*\": [\"./src/hooks/*\"],\n      \"@/config/*\": [\"./src/config/*\"],\n      \"@/styles/*\": [\"./src/styles/*\"],\n      \"@/lib/*\": [\"./src/lib/*\"],\n      \"@/components/*\": [\"./src/components/*\"]\n    },\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ]\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  }
]