Repository: joschan21/image-alt-generator Branch: main Commit: 12dcac422023 Files: 71 Total size: 126.6 KB Directory structure: gitextract_prl9_fiq/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .vscode/ │ └── settings.json ├── README.md ├── next-env.d.ts ├── next.config.mjs ├── package.json ├── postcss.config.js ├── prettier.config.js ├── src/ │ ├── app/ │ │ ├── head.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── privacy-policy/ │ │ │ ├── head.tsx │ │ │ └── page.tsx │ │ └── terms/ │ │ ├── head.tsx │ │ └── page.tsx │ ├── components/ │ │ ├── icons.tsx │ │ ├── main-nav.tsx │ │ ├── site-header.tsx │ │ └── ui/ │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── file-input.tsx │ │ ├── hover-card.tsx │ │ ├── image-upload.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── slider.tsx │ │ ├── spinner.tsx │ │ ├── switch.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── tooltip.tsx │ ├── config/ │ │ ├── image.ts │ │ ├── s3.ts │ │ └── site.ts │ ├── hooks/ │ │ ├── use-s3-upload.ts │ │ ├── use-toast.ts │ │ └── use-upload-file.ts │ ├── lib/ │ │ ├── api-middlewares/ │ │ │ └── with-methods.ts │ │ ├── exceptions.ts │ │ ├── s3.ts │ │ ├── utils.ts │ │ └── validations/ │ │ └── s3.ts │ ├── pages/ │ │ └── api/ │ │ └── image/ │ │ ├── presign.ts │ │ └── process.ts │ ├── styles/ │ │ └── globals.css │ └── types/ │ ├── api/ │ │ └── image.ts │ └── nav.ts ├── tailwind.config.js └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # editorconfig.org root = true [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintignore ================================================ dist/* .cache public node_modules *.esm.js ================================================ FILE: .eslintrc.json ================================================ { "$schema": "https://json.schemastore.org/eslintrc", "root": true, "extends": [ "next/core-web-vitals", "prettier", "plugin:tailwindcss/recommended" ], "plugins": ["tailwindcss"], "rules": { "tailwindcss/classnames-order": "off", "@next/next/no-html-link-for-pages": "off", "react/jsx-key": "off", "tailwindcss/no-custom-classname": "off", "react-hooks/exhaustive-deps": "off" }, "settings": { "tailwindcss": { "callees": ["cn"] } } } ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies node_modules .pnp .pnp.js # testing coverage # next.js .next/ out/ build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* .pnpm-debug.log* # local env files .env.local .env.development.local .env.test.local .env.production.local # turbo .turbo .contentlayer .env ================================================ FILE: .prettierignore ================================================ cache .cache package.json package-lock.json public CHANGELOG.md .yarn ================================================ FILE: .vscode/settings.json ================================================ { "typescript.tsdk": "node_modules\\typescript\\lib", "typescript.enablePromptUseWorkspaceTsdk": true } ================================================ FILE: README.md ================================================ # Next alt generator A Next.js 13 project for generating image alt tags automatically and in bulk. ## Features - Radix UI Primitives - Tailwind CSS - Fonts with `@next/font` - Icons from [Lucide](https://lucide.dev) - Dark mode with `next-themes` - Automatic import sorting with `@ianvs/prettier-plugin-sort-imports` ## Tailwind CSS Features - Class merging with `taiwind-merge` - Animation with `tailwindcss-animate` - Conditional classes with `clsx` - Variants with `class-variance-authority` - Automatic class sorting with `eslint-plugin-tailwindcss` ## Import Sort The starter comes with `@ianvs/prettier-plugin-sort-imports` for automatically sort your imports. ### Input ```tsx import * as React from "react" import Link from "next/link" import { siteConfig } from "@/config/site" import { buttonVariants } from "@/components/ui/button" import "@/styles/globals.css" import { twMerge } from "tailwind-merge" import { NavItem } from "@/types/nav" import { cn } from "@/lib/utils" ``` ### Output ```tsx import * as React from "react" // React is always first. import Link from "next/link" // Followed by next modules. import { twMerge } from "tailwind-merge" // Followed by third-party modules // Space import "@/styles/globals.css" // styles import { NavItem } from "@/types/nav" // types import { siteConfig } from "@/config/site" // config import { cn } from "@/lib/utils" // lib import { buttonVariants } from "@/components/ui/button" // components ``` ### Class Merging The `cn` util handles conditional classes and class merging. ### Input ```ts cn("px-2 bg-slate-100 py-2 bg-slate-200") // Outputs `p-2 bg-slate-200` ``` ## License & Credits Licensed under the [MIT license](https://opensource.org/license/mit/). Boilerplate project template made by [shadcn](https://github.com/shadcn/next-template) ================================================ FILE: next-env.d.ts ================================================ /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. ================================================ FILE: next.config.mjs ================================================ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, experimental: { appDir: true, fontLoaders: [ { loader: "@next/font/google", options: { subsets: ["latin"] }, }, ], }, images: { domains: ["image-to-alt.s3.eu-central-1.amazonaws.com"], }, } export default nextConfig ================================================ FILE: package.json ================================================ { "name": "next-template", "version": "0.0.1", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "preview": "next build && next start" }, "dependencies": { "@next/font": "^13.1.6", "@radix-ui/react-accessible-icon": "^1.0.1", "@radix-ui/react-accordion": "^1.1.0", "@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-aspect-ratio": "^1.0.1", "@radix-ui/react-avatar": "^1.0.1", "@radix-ui/react-checkbox": "^1.0.1", "@radix-ui/react-collapsible": "^1.0.1", "@radix-ui/react-context-menu": "^2.1.1", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.1", "@radix-ui/react-hover-card": "^1.0.3", "@radix-ui/react-label": "^2.0.0", "@radix-ui/react-menubar": "^1.0.0", "@radix-ui/react-navigation-menu": "^1.1.1", "@radix-ui/react-popover": "^1.0.2", "@radix-ui/react-progress": "^1.0.1", "@radix-ui/react-radio-group": "^1.1.0", "@radix-ui/react-scroll-area": "^1.0.2", "@radix-ui/react-select": "^1.2.0", "@radix-ui/react-separator": "^1.0.1", "@radix-ui/react-slider": "^1.1.0", "@radix-ui/react-slot": "^1.0.1", "@radix-ui/react-switch": "^1.0.1", "@radix-ui/react-tabs": "^1.0.2", "@radix-ui/react-toast": "^1.1.2", "@radix-ui/react-toggle-group": "^1.0.1", "@radix-ui/react-tooltip": "^1.0.3", "aws-sdk": "^2.1318.0", "axios": "^1.3.3", "class-variance-authority": "^0.4.0", "clsx": "^1.2.1", "lucide-react": "0.105.0-alpha.4", "nanoid": "^4.0.1", "next": "^13.1.6", "next-themes": "^0.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", "sharp": "^0.31.3", "tailwind-merge": "^1.8.0", "tailwindcss-animate": "^1.0.5", "zod": "^3.20.6" }, "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^3.7.1", "@types/node": "^17.0.12", "@types/react": "^18.0.22", "@types/react-dom": "^18.0.7", "autoprefixer": "^10.4.13", "eslint": "^8.31.0", "eslint-config-next": "13.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-react": "^7.31.11", "eslint-plugin-tailwindcss": "^3.8.0", "postcss": "^8.4.14", "prettier": "^2.7.1", "tailwindcss": "^3.1.7", "typescript": "^4.5.3" } } ================================================ FILE: postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: prettier.config.js ================================================ /** @type {import('prettier').Config} */ module.exports = { endOfLine: "lf", semi: false, singleQuote: true, tabWidth: 2, trailingComma: "es5", importOrder: [ "^(react/(.*)$)|^(react$)", "^(next/(.*)$)|^(next$)", "", "", "^types$", "^@/types/(.*)$", "^@/config/(.*)$", "^@/lib/(.*)$", "^@/components/(.*)$", "^@/styles/(.*)$", "^[./]", ], importOrderSeparation: false, importOrderSortSpecifiers: true, importOrderBuiltinModulesToTop: true, importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"], importOrderMergeDuplicateImports: true, importOrderCombineTypeAndValueImports: true, plugins: ["@ianvs/prettier-plugin-sort-imports"], } ================================================ FILE: src/app/head.tsx ================================================ import { FC } from 'react' const head: FC = () => { return ImageToAlt - Generate alt tags from images } export default head ================================================ FILE: src/app/layout.tsx ================================================ import { Inter as FontSans } from '@next/font/google' import '@/styles/globals.css' import { Toaster } from '@/ui/toaster' import { cn } from '@/lib/utils' import { SiteHeader } from '../components/site-header' import { TooltipProvider } from '../components/ui/tooltip' const fontSans = FontSans({ subsets: ['latin'], variable: '--font-inter', }) interface RootLayoutProps { children: React.ReactNode } export default function RootLayout({ children }: RootLayoutProps) { return ( <>
{children}
) } ================================================ FILE: src/app/page.tsx ================================================ 'use client' import { FC } from 'react' import { Button, buttonVariants } from '@/ui/button' import { FileInput } from '@/ui/file-input' import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip' export const metadata = { title: 'ImageToAlt - Home', } const page: FC = () => { return (

Easily create alt-descriptions
for your images.

Bulk-generate SEO-optimized alt-descriptions that you can copy and paste into your app. Free & open-source.

Available soon

{/* Legal disclaimers */}

All images are used solely for alt-generation and are automatically deleted after 24h.

) } export default page ================================================ FILE: src/app/privacy-policy/head.tsx ================================================ import { FC } from 'react' const head: FC = () => { return Privacy Policy - ImageToAlt } export default head ================================================ FILE: src/app/privacy-policy/page.tsx ================================================ import { FC } from 'react' import Link from 'next/link' const page: FC = () => { return (

ImageToAlt Privacy Policy

ImageToAlt is a free online service that provides a simple way to generate an alt tag (a description of what is visible on an image, determined by a machine learning algorithm) from an image. In order to provide this service, we need to collect and store images on our servers.

Information We Collect

When you use the App, we automatically collect certain information about your device, including information about your web browser, IP address, time zone, and some of the cookies that are installed on your device. We refer to this automatically-collected information as "Device Information".

We collect Device Information using the following technologies:

  • Cookies: Cookies are data files that are placed on your device or computer and often include an anonymous unique identifier.
  • Log files: Log files track actions occurring on the App, and collect data including your IP address, browser type, Internet service provider, referring/exit pages, and date/time stamps.

When you upload an image to the App, we collect the image itself. We use the image to generate an alt tag and store the alt tag. The image is then automatically deleted after 24 hours.

How We Use Your Information

We use the images you upload to our servers solely for the purpose of generating an alt tag and serving it back to you. We do not use your images for any other purpose. To provide this service, we use a machine learning algorithm provided by Replicate, Inc. You can read more about how Replicate, Inc. uses your data{' '} here . We require all third-party providers to have adequate technical and organizational measures in place to ensure the security of user data. We do not share user data with any other third parties.

How We Protect Your Information

We take the security of your information very seriously. All images uploaded to our servers are stored in a secure location in Germany. We do not share your images with any third parties. We also automatically delete all images from our servers after 24 hours.

Use of Cookies

We do not use any cookies to track user behavior. We only use session cookies to manage your session on our website.

Changes to Our Privacy Policy

We reserve the right to make changes to this Privacy Policy at any time. Any changes will be posted on this page, so please check back periodically for updates.

Contact Us

If you have any questions about this Privacy Policy, please contact us at admin@wordful.ai.

) } export default page ================================================ FILE: src/app/terms/head.tsx ================================================ import { FC } from 'react' const head: FC = () => { return Terms and Conditions - ImageToAlt } export default head ================================================ FILE: src/app/terms/page.tsx ================================================ import { FC } from 'react' import Head from 'next/head' const page: FC = () => { return (
ImageToAlt - Terms and Conditions

ImageToAlt Terms and Conditions

These terms and conditions ("Terms") apply to your use of the ImageToAlt app ("App") and the alt tag generation service ("Service") provided by ImageToAlt ("we" or "us"). By using the App or the Service, you agree to be bound by these Terms.

Use of the App and the Service

The App and the Service are provided for informational purposes only. You may use the App and the Service at your own risk, and we shall not be liable for any damages or harm that may arise from your use of the App or the Service.

Intellectual Property

The App and the Service, including any content or materials made available through the App or the Service, are protected by copyright and other intellectual property laws. You may not copy, modify, distribute, sell, or lease any part of the App or the Service without our prior written consent.

Disclaimer of Liability

The App and the Service are provided "as is" and without warranty of any kind. We make no representations or warranties of any kind, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the App or the Service or the information, products, services, or related graphics contained in the App or the Service for any purpose. To the fullest extent permitted by law, we disclaim any and all warranties, express or implied, including, but not limited to, implied warranties of merchantability and fitness for a particular purpose.

In no event shall ImageToAlt be liable for any direct, indirect, incidental, consequential, special or exemplary damages, including, but not limited to, damages for loss of profits, goodwill, use, data or other intangible losses resulting from the use of or inability to use the App or the Service.

Indemnification

You agree to indemnify and hold ImageToAlt, its affiliates, officers, agents, and other partners and employees, harmless from any loss, liability, claim or demand, including reasonable attorneys' fees, made by any third party due to or arising out of your use of the App or the Service.

Termination

We may terminate your access to the App and the Service at any time, without cause or notice.

Governing Law

These Terms and your use of the App and the Service shall be governed by and construed in accordance with the laws of Germany, without giving effect to any principles of conflicts of law.

Changes to these Terms

We reserve the right to modify these Terms at any time. If we make changes to these Terms, we will post the revised Terms on the App and update the "Last Updated" date at the top of these Terms. By continuing to use the App and the Service after the revised Terms become effective, you agree to be bound by the revised Terms.

Contact Us

If you have any questions about these Terms or the App or the Service, please contact us at admin@wordful.ai.

Last Updated: Feb 20th, 2023

) } export default page ================================================ FILE: src/components/icons.tsx ================================================ import { Laptop, LucideProps, Moon, SunMedium, type Icon as LucideIcon, } from 'lucide-react' export type Icon = LucideIcon export const Icons = { sun: SunMedium, moon: Moon, laptop: Laptop, youtube: (props: LucideProps) => ( ), logo: (props: LucideProps) => ( ), gitHub: (props: LucideProps) => ( ), plus: (props: LucideProps) => ( ), redx: (props: LucideProps) => ( ), } ================================================ FILE: src/components/main-nav.tsx ================================================ import Link from 'next/link' import { NavItem } from '@/src/types/nav' import { siteConfig } from '@/config/site' import { cn } from '@/lib/utils' import { Icons } from '@/components/icons' import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' interface MainNavProps { items?: NavItem[] } export function MainNav({ items }: MainNavProps) { return (
{siteConfig.name} {items?.length ? ( ) : null} {siteConfig.name} {items?.map( (item, index) => item.href && ( {item.title} ) )}
) } ================================================ FILE: src/components/site-header.tsx ================================================ import Link from 'next/link' import { siteConfig } from '@/config/site' import { Icons } from '@/components/icons' import { MainNav } from '@/components/main-nav' import { buttonVariants } from '@/components/ui/button' export function SiteHeader() { return (
) } ================================================ FILE: src/components/ui/accordion.tsx ================================================ 'use client' import * as React from 'react' import * as AccordionPrimitive from '@radix-ui/react-accordion' import { ChevronDown } from 'lucide-react' import { cn } from '@/lib/utils' const Accordion = AccordionPrimitive.Root const AccordionItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AccordionItem.displayName = 'AccordionItem' const AccordionTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( svg]:rotate-180', className )} {...props} > {children} )) AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName const AccordionContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => (
{children}
)) AccordionContent.displayName = AccordionPrimitive.Content.displayName export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } ================================================ FILE: src/components/ui/alert-dialog.tsx ================================================ 'use client' import * as React from 'react' import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' import { cn } from '@/lib/utils' const AlertDialog = AlertDialogPrimitive.Root const AlertDialogTrigger = AlertDialogPrimitive.Trigger const AlertDialogPortal = ({ className, children, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
{children}
) AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName const AlertDialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( )) AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName const AlertDialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
) AlertDialogHeader.displayName = 'AlertDialogHeader' const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
) AlertDialogFooter.displayName = 'AlertDialogFooter' const AlertDialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName const AlertDialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName const AlertDialogAction = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName const AlertDialogCancel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName export { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, } ================================================ FILE: src/components/ui/aspect-ratio.tsx ================================================ 'use client' import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio' const AspectRatio = AspectRatioPrimitive.Root export { AspectRatio } ================================================ FILE: src/components/ui/avatar.tsx ================================================ 'use client' import * as React from 'react' import * as AvatarPrimitive from '@radix-ui/react-avatar' import { cn } from '@/lib/utils' const Avatar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) Avatar.displayName = AvatarPrimitive.Root.displayName const AvatarImage = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AvatarImage.displayName = AvatarPrimitive.Image.displayName const AvatarFallback = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName export { Avatar, AvatarImage, AvatarFallback } ================================================ FILE: src/components/ui/button.tsx ================================================ import * as React from 'react' import Link from 'next/link' import { VariantProps, cva } from 'class-variance-authority' import { cn } from '@/lib/utils' const buttonVariants = cva( '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', { variants: { variant: { default: 'bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900', destructive: 'bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600', outline: 'bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100', subtle: 'bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100', ghost: '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', 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', }, size: { default: 'h-10 py-2 px-4', sm: 'h-9 px-2 rounded-md', lg: 'h-11 px-8 rounded-md', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { href?: string } const Button = React.forwardRef( ({ className, children, href, variant, size, ...props }, ref) => { if (href) { return ( {children} ) } return ( ) } ) Button.displayName = 'Button' export { Button, buttonVariants } ================================================ FILE: src/components/ui/checkbox.tsx ================================================ 'use client' import * as React from 'react' import * as CheckboxPrimitive from '@radix-ui/react-checkbox' import { Check } from 'lucide-react' import { cn } from '@/lib/utils' const Checkbox = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) Checkbox.displayName = CheckboxPrimitive.Root.displayName export { Checkbox } ================================================ FILE: src/components/ui/collapsible.tsx ================================================ 'use client' import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' const Collapsible = CollapsiblePrimitive.Root const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent export { Collapsible, CollapsibleTrigger, CollapsibleContent } ================================================ FILE: src/components/ui/context-menu.tsx ================================================ 'use client' import * as React from 'react' import * as ContextMenuPrimitive from '@radix-ui/react-context-menu' import { Check, ChevronRight, Circle } from 'lucide-react' import { cn } from '@/lib/utils' const ContextMenu = ContextMenuPrimitive.Root const ContextMenuTrigger = ContextMenuPrimitive.Trigger const ContextMenuGroup = ContextMenuPrimitive.Group const ContextMenuPortal = ContextMenuPrimitive.Portal const ContextMenuSub = ContextMenuPrimitive.Sub const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup const ContextMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, children, ...props }, ref) => ( {children} )) ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName const ContextMenuSubContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName const ContextMenuContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName const ContextMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( )) ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName const ContextMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( {children} )) ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName const ContextMenuRadioItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} )) ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName const ContextMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( )) ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName const ContextMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( ) } ContextMenuShortcut.displayName = 'ContextMenuShortcut' export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup, } ================================================ FILE: src/components/ui/dialog.tsx ================================================ 'use client' import * as React from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' import { X } from 'lucide-react' import { cn } from '@/lib/utils' const Dialog = DialogPrimitive.Root const DialogTrigger = DialogPrimitive.Trigger const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => (
{children}
) DialogPortal.displayName = DialogPrimitive.Portal.displayName const DialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( )) DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} Close )) DialogContent.displayName = DialogPrimitive.Content.displayName const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
) DialogHeader.displayName = 'DialogHeader' const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
) DialogFooter.displayName = 'DialogFooter' const DialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DialogTitle.displayName = DialogPrimitive.Title.displayName const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DialogDescription.displayName = DialogPrimitive.Description.displayName export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } ================================================ FILE: src/components/ui/dropdown-menu.tsx ================================================ 'use client' import * as React from 'react' import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' import { Check, ChevronRight, Circle } from 'lucide-react' import { cn } from '@/lib/utils' const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger const DropdownMenuGroup = DropdownMenuPrimitive.Group const DropdownMenuPortal = DropdownMenuPrimitive.Portal const DropdownMenuSub = DropdownMenuPrimitive.Sub const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup const DropdownMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, children, ...props }, ref) => ( {children} )) DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName const DropdownMenuSubContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName const DropdownMenuContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, sideOffset = 4, ...props }, ref) => ( )) DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName const DropdownMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( )) DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName const DropdownMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, checked, ...props }, ref) => ( {children} )) DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName const DropdownMenuRadioItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( {children} )) DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName const DropdownMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({ className, inset, ...props }, ref) => ( )) DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName const DropdownMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )) DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( ) } DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, } ================================================ FILE: src/components/ui/file-input.tsx ================================================ 'use client' import { forwardRef, useReducer, useState, type ChangeEvent, type DragEvent, } from 'react' import { useS3Upload } from '@/src/hooks/use-s3-upload' import { useToast } from '@/src/hooks/use-toast' import ImageUpload from '@/ui/image-upload' import { MAX_FILE_SIZE } from '@/config/image' import { cn, validateFileType } from '@/lib/utils' import { Icons } from '../icons' interface FileWithUrl { name: string getUrl: string size: number error?: boolean | undefined } // Reducer action(s) const addFilesToInput = () => ({ type: 'ADD_FILES_TO_INPUT' as const, payload: [] as FileWithUrl[], }) type Action = ReturnType type State = FileWithUrl[] export interface InputProps extends Omit, 'type'> {} const FileInput = forwardRef( ({ className, ...props }, ref) => { const { toast } = useToast() const { s3Upload } = useS3Upload() const [dragActive, setDragActive] = useState(false) const [input, dispatch] = useReducer((state: State, action: Action) => { switch (action.type) { case 'ADD_FILES_TO_INPUT': { // do not allow more than 5 files to be uploaded at once if (state.length + action.payload.length > 10) { toast({ title: 'Too many files', description: 'You can only upload a maximum of 5 files at a time.', }) return state } return [...state, ...action.payload] } // You could extend this, for example to allow removing files } }, []) const noInput = input.length === 0 // handle drag events const handleDrag = (e: DragEvent) => { e.preventDefault() e.stopPropagation() if (e.type === 'dragenter' || e.type === 'dragover') { setDragActive(true) } else if (e.type === 'dragleave') { setDragActive(false) } } // triggers when file is selected with click const handleChange = async (e: ChangeEvent) => { e.preventDefault() try { if (e.target.files && e.target.files[0]) { // at least one file has been selected // validate file type const valid = validateFileType(e.target.files[0]) if (!valid) { toast({ title: 'Invalid file type', description: 'Please upload a valid file type.', }) return } const { getUrl, error } = await s3Upload(e.target.files[0]) if (!getUrl || error) throw new Error('Error uploading file') const { name, size } = e.target.files[0] addFilesToState([{ name, getUrl, size }]) } } catch (error) { // already handled } } const addFilesToState = (files: FileWithUrl[]) => { dispatch({ type: 'ADD_FILES_TO_INPUT', payload: files }) } // triggers when file is dropped const handleDrop = async (e: DragEvent) => { e.preventDefault() e.stopPropagation() // validate file type if (e.dataTransfer.files && e.dataTransfer.files[0]) { const files = Array.from(e.dataTransfer.files) const validFiles = files.filter((file) => validateFileType(file)) if (files.length !== validFiles.length) { toast({ title: 'Invalid file type', description: 'Only image files are allowed.', }) } try { const filesWithUrl = await Promise.all( validFiles.map(async (file) => { const { name, size } = file const { getUrl, error } = await s3Upload(file) if (!getUrl || error) return { name, size, getUrl: '', error } return { name, size, getUrl } }) ) setDragActive(false) // at least one file has been selected addFilesToState(filesWithUrl) e.dataTransfer.clearData() } catch (error) { // already handled } } } return (
e.preventDefault()} onDragEnter={handleDrag} className="flex h-full items-center w-full lg:w-2/3 justify-start" >