finished add wine card until db scheme is done
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Vercel Preview Deployment / Deploy-Preview (push) Successful in 2m16s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Vercel Preview Deployment / Deploy-Preview (push) Successful in 2m16s
				
			This commit is contained in:
		
							parent
							
								
									a04ceb616c
								
							
						
					
					
						commit
						d7ebcb8558
					
				
							
								
								
									
										59
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										59
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -10,6 +10,7 @@
 | 
				
			|||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@radix-ui/react-select": "^2.0.0",
 | 
					        "@radix-ui/react-select": "^2.0.0",
 | 
				
			||||||
        "@radix-ui/react-slot": "^1.0.2",
 | 
					        "@radix-ui/react-slot": "^1.0.2",
 | 
				
			||||||
 | 
					        "@radix-ui/react-toast": "^1.1.5",
 | 
				
			||||||
        "@t3-oss/env-nextjs": "^0.10.1",
 | 
					        "@t3-oss/env-nextjs": "^0.10.1",
 | 
				
			||||||
        "@vercel/postgres": "^0.8.0",
 | 
					        "@vercel/postgres": "^0.8.0",
 | 
				
			||||||
        "class-variance-authority": "^0.7.0",
 | 
					        "class-variance-authority": "^0.7.0",
 | 
				
			||||||
@ -2025,6 +2026,30 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@radix-ui/react-presence": {
 | 
				
			||||||
 | 
					      "version": "1.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@babel/runtime": "^7.13.10",
 | 
				
			||||||
 | 
					        "@radix-ui/react-compose-refs": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-use-layout-effect": "1.0.1"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@types/react": "*",
 | 
				
			||||||
 | 
					        "@types/react-dom": "*",
 | 
				
			||||||
 | 
					        "react": "^16.8 || ^17.0 || ^18.0",
 | 
				
			||||||
 | 
					        "react-dom": "^16.8 || ^17.0 || ^18.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependenciesMeta": {
 | 
				
			||||||
 | 
					        "@types/react": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "@types/react-dom": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@radix-ui/react-primitive": {
 | 
					    "node_modules/@radix-ui/react-primitive": {
 | 
				
			||||||
      "version": "1.0.3",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
 | 
				
			||||||
@ -2109,6 +2134,40 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@radix-ui/react-toast": {
 | 
				
			||||||
 | 
					      "version": "1.1.5",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@babel/runtime": "^7.13.10",
 | 
				
			||||||
 | 
					        "@radix-ui/primitive": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-collection": "1.0.3",
 | 
				
			||||||
 | 
					        "@radix-ui/react-compose-refs": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-context": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-dismissable-layer": "1.0.5",
 | 
				
			||||||
 | 
					        "@radix-ui/react-portal": "1.0.4",
 | 
				
			||||||
 | 
					        "@radix-ui/react-presence": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-primitive": "1.0.3",
 | 
				
			||||||
 | 
					        "@radix-ui/react-use-callback-ref": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-use-controllable-state": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-use-layout-effect": "1.0.1",
 | 
				
			||||||
 | 
					        "@radix-ui/react-visually-hidden": "1.0.3"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@types/react": "*",
 | 
				
			||||||
 | 
					        "@types/react-dom": "*",
 | 
				
			||||||
 | 
					        "react": "^16.8 || ^17.0 || ^18.0",
 | 
				
			||||||
 | 
					        "react-dom": "^16.8 || ^17.0 || ^18.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependenciesMeta": {
 | 
				
			||||||
 | 
					        "@types/react": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "@types/react-dom": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@radix-ui/react-use-callback-ref": {
 | 
					    "node_modules/@radix-ui/react-use-callback-ref": {
 | 
				
			||||||
      "version": "1.0.1",
 | 
					      "version": "1.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@
 | 
				
			|||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@radix-ui/react-select": "^2.0.0",
 | 
					    "@radix-ui/react-select": "^2.0.0",
 | 
				
			||||||
    "@radix-ui/react-slot": "^1.0.2",
 | 
					    "@radix-ui/react-slot": "^1.0.2",
 | 
				
			||||||
 | 
					    "@radix-ui/react-toast": "^1.1.5",
 | 
				
			||||||
    "@t3-oss/env-nextjs": "^0.10.1",
 | 
					    "@t3-oss/env-nextjs": "^0.10.1",
 | 
				
			||||||
    "@vercel/postgres": "^0.8.0",
 | 
					    "@vercel/postgres": "^0.8.0",
 | 
				
			||||||
    "class-variance-authority": "^0.7.0",
 | 
					    "class-variance-authority": "^0.7.0",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,26 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Card,
 | 
				
			||||||
 | 
					  CardContent,
 | 
				
			||||||
 | 
					  CardDescription,
 | 
				
			||||||
 | 
					  CardFooter,
 | 
				
			||||||
 | 
					  CardHeader,
 | 
				
			||||||
 | 
					  CardTitle,
 | 
				
			||||||
 | 
					} from "~/components/ui/card";
 | 
				
			||||||
import CreateWine from "./CreateWine";
 | 
					import CreateWine from "./CreateWine";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function FormCard() {
 | 
					export default function FormCard() {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="flex flex-col justify-start shadow-md">
 | 
					    <Card>
 | 
				
			||||||
      <CreateWine />
 | 
					      <CardHeader>
 | 
				
			||||||
    </div>
 | 
					        <CardTitle>Add wine</CardTitle>
 | 
				
			||||||
 | 
					        <CardDescription>All fields are required so get to it!</CardDescription>
 | 
				
			||||||
 | 
					      </CardHeader>
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
 | 
					        <CreateWine />
 | 
				
			||||||
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					      <CardFooter>
 | 
				
			||||||
 | 
					        <p>Card Footer</p>
 | 
				
			||||||
 | 
					      </CardFooter>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								src/components/ui/card.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/components/ui/card.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Card = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "rounded-lg border bg-card text-card-foreground shadow-sm",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					Card.displayName = "Card"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardHeader = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex flex-col space-y-1.5 p-6", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardHeader.displayName = "CardHeader"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLHeadingElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <h3
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "text-2xl font-semibold leading-none tracking-tight",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardTitle.displayName = "CardTitle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLParagraphElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLParagraphElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <p
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm text-muted-foreground", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardDescription.displayName = "CardDescription"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardContent = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardContent.displayName = "CardContent"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CardFooter = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLDivElement,
 | 
				
			||||||
 | 
					  React.HTMLAttributes<HTMLDivElement>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex items-center p-6 pt-0", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					CardFooter.displayName = "CardFooter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
 | 
				
			||||||
							
								
								
									
										129
									
								
								src/components/ui/toast.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/components/ui/toast.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					import * as ToastPrimitives from "@radix-ui/react-toast"
 | 
				
			||||||
 | 
					import { cva, type VariantProps } from "class-variance-authority"
 | 
				
			||||||
 | 
					import { X } from "lucide-react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { cn } from "~/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastProvider = ToastPrimitives.Provider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastViewport = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Viewport>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Viewport
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastViewport.displayName = ToastPrimitives.Viewport.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toastVariants = cva(
 | 
				
			||||||
 | 
					  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    variants: {
 | 
				
			||||||
 | 
					      variant: {
 | 
				
			||||||
 | 
					        default: "border bg-background text-foreground",
 | 
				
			||||||
 | 
					        destructive:
 | 
				
			||||||
 | 
					          "destructive group border-destructive bg-destructive text-destructive-foreground",
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    defaultVariants: {
 | 
				
			||||||
 | 
					      variant: "default",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Toast = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Root>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
 | 
				
			||||||
 | 
					    VariantProps<typeof toastVariants>
 | 
				
			||||||
 | 
					>(({ className, variant, ...props }, ref) => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ToastPrimitives.Root
 | 
				
			||||||
 | 
					      ref={ref}
 | 
				
			||||||
 | 
					      className={cn(toastVariants({ variant }), className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					Toast.displayName = ToastPrimitives.Root.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastAction = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Action>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Action
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastAction.displayName = ToastPrimitives.Action.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastClose = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Close>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Close
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn(
 | 
				
			||||||
 | 
					      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
 | 
				
			||||||
 | 
					      className
 | 
				
			||||||
 | 
					    )}
 | 
				
			||||||
 | 
					    toast-close=""
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <X className="h-4 w-4" />
 | 
				
			||||||
 | 
					  </ToastPrimitives.Close>
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastClose.displayName = ToastPrimitives.Close.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastTitle = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Title>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Title
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm font-semibold", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastTitle.displayName = ToastPrimitives.Title.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ToastDescription = React.forwardRef<
 | 
				
			||||||
 | 
					  React.ElementRef<typeof ToastPrimitives.Description>,
 | 
				
			||||||
 | 
					  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ToastPrimitives.Description
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("text-sm opacity-90", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					))
 | 
				
			||||||
 | 
					ToastDescription.displayName = ToastPrimitives.Description.displayName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToastActionElement = React.ReactElement<typeof ToastAction>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  type ToastProps,
 | 
				
			||||||
 | 
					  type ToastActionElement,
 | 
				
			||||||
 | 
					  ToastProvider,
 | 
				
			||||||
 | 
					  ToastViewport,
 | 
				
			||||||
 | 
					  Toast,
 | 
				
			||||||
 | 
					  ToastTitle,
 | 
				
			||||||
 | 
					  ToastDescription,
 | 
				
			||||||
 | 
					  ToastClose,
 | 
				
			||||||
 | 
					  ToastAction,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/components/ui/toaster.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Toast,
 | 
				
			||||||
 | 
					  ToastClose,
 | 
				
			||||||
 | 
					  ToastDescription,
 | 
				
			||||||
 | 
					  ToastProvider,
 | 
				
			||||||
 | 
					  ToastTitle,
 | 
				
			||||||
 | 
					  ToastViewport,
 | 
				
			||||||
 | 
					} from "~/components/ui/toast"
 | 
				
			||||||
 | 
					import { useToast } from "~/components/ui/use-toast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Toaster() {
 | 
				
			||||||
 | 
					  const { toasts } = useToast()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ToastProvider>
 | 
				
			||||||
 | 
					      {toasts.map(function ({ id, title, description, action, ...props }) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Toast key={id} {...props}>
 | 
				
			||||||
 | 
					            <div className="grid gap-1">
 | 
				
			||||||
 | 
					              {title && <ToastTitle>{title}</ToastTitle>}
 | 
				
			||||||
 | 
					              {description && (
 | 
				
			||||||
 | 
					                <ToastDescription>{description}</ToastDescription>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {action}
 | 
				
			||||||
 | 
					            <ToastClose />
 | 
				
			||||||
 | 
					          </Toast>
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      })}
 | 
				
			||||||
 | 
					      <ToastViewport />
 | 
				
			||||||
 | 
					    </ToastProvider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										194
									
								
								src/components/ui/use-toast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								src/components/ui/use-toast.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					"use client"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Inspired by react-hot-toast library
 | 
				
			||||||
 | 
					import * as React from "react"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ToastActionElement,
 | 
				
			||||||
 | 
					  ToastProps,
 | 
				
			||||||
 | 
					} from "~/components/ui/toast"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TOAST_LIMIT = 1
 | 
				
			||||||
 | 
					const TOAST_REMOVE_DELAY = 1000000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ToasterToast = ToastProps & {
 | 
				
			||||||
 | 
					  id: string
 | 
				
			||||||
 | 
					  title?: React.ReactNode
 | 
				
			||||||
 | 
					  description?: React.ReactNode
 | 
				
			||||||
 | 
					  action?: ToastActionElement
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const actionTypes = {
 | 
				
			||||||
 | 
					  ADD_TOAST: "ADD_TOAST",
 | 
				
			||||||
 | 
					  UPDATE_TOAST: "UPDATE_TOAST",
 | 
				
			||||||
 | 
					  DISMISS_TOAST: "DISMISS_TOAST",
 | 
				
			||||||
 | 
					  REMOVE_TOAST: "REMOVE_TOAST",
 | 
				
			||||||
 | 
					} as const
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let count = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function genId() {
 | 
				
			||||||
 | 
					  count = (count + 1) % Number.MAX_SAFE_INTEGER
 | 
				
			||||||
 | 
					  return count.toString()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ActionType = typeof actionTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Action =
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["ADD_TOAST"]
 | 
				
			||||||
 | 
					      toast: ToasterToast
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["UPDATE_TOAST"]
 | 
				
			||||||
 | 
					      toast: Partial<ToasterToast>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["DISMISS_TOAST"]
 | 
				
			||||||
 | 
					      toastId?: ToasterToast["id"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: ActionType["REMOVE_TOAST"]
 | 
				
			||||||
 | 
					      toastId?: ToasterToast["id"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface State {
 | 
				
			||||||
 | 
					  toasts: ToasterToast[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const addToRemoveQueue = (toastId: string) => {
 | 
				
			||||||
 | 
					  if (toastTimeouts.has(toastId)) {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const timeout = setTimeout(() => {
 | 
				
			||||||
 | 
					    toastTimeouts.delete(toastId)
 | 
				
			||||||
 | 
					    dispatch({
 | 
				
			||||||
 | 
					      type: "REMOVE_TOAST",
 | 
				
			||||||
 | 
					      toastId: toastId,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }, TOAST_REMOVE_DELAY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  toastTimeouts.set(toastId, timeout)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const reducer = (state: State, action: Action): State => {
 | 
				
			||||||
 | 
					  switch (action.type) {
 | 
				
			||||||
 | 
					    case "ADD_TOAST":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "UPDATE_TOAST":
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.map((t) =>
 | 
				
			||||||
 | 
					          t.id === action.toast.id ? { ...t, ...action.toast } : t
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case "DISMISS_TOAST": {
 | 
				
			||||||
 | 
					      const { toastId } = action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // ! Side effects ! - This could be extracted into a dismissToast() action,
 | 
				
			||||||
 | 
					      // but I'll keep it here for simplicity
 | 
				
			||||||
 | 
					      if (toastId) {
 | 
				
			||||||
 | 
					        addToRemoveQueue(toastId)
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        state.toasts.forEach((toast) => {
 | 
				
			||||||
 | 
					          addToRemoveQueue(toast.id)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.map((t) =>
 | 
				
			||||||
 | 
					          t.id === toastId || toastId === undefined
 | 
				
			||||||
 | 
					            ? {
 | 
				
			||||||
 | 
					                ...t,
 | 
				
			||||||
 | 
					                open: false,
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            : t
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    case "REMOVE_TOAST":
 | 
				
			||||||
 | 
					      if (action.toastId === undefined) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          ...state,
 | 
				
			||||||
 | 
					          toasts: [],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        ...state,
 | 
				
			||||||
 | 
					        toasts: state.toasts.filter((t) => t.id !== action.toastId),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const listeners: Array<(state: State) => void> = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let memoryState: State = { toasts: [] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function dispatch(action: Action) {
 | 
				
			||||||
 | 
					  memoryState = reducer(memoryState, action)
 | 
				
			||||||
 | 
					  listeners.forEach((listener) => {
 | 
				
			||||||
 | 
					    listener(memoryState)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Toast = Omit<ToasterToast, "id">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function toast({ ...props }: Toast) {
 | 
				
			||||||
 | 
					  const id = genId()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const update = (props: ToasterToast) =>
 | 
				
			||||||
 | 
					    dispatch({
 | 
				
			||||||
 | 
					      type: "UPDATE_TOAST",
 | 
				
			||||||
 | 
					      toast: { ...props, id },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dispatch({
 | 
				
			||||||
 | 
					    type: "ADD_TOAST",
 | 
				
			||||||
 | 
					    toast: {
 | 
				
			||||||
 | 
					      ...props,
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					      open: true,
 | 
				
			||||||
 | 
					      onOpenChange: (open) => {
 | 
				
			||||||
 | 
					        if (!open) dismiss()
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id: id,
 | 
				
			||||||
 | 
					    dismiss,
 | 
				
			||||||
 | 
					    update,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function useToast() {
 | 
				
			||||||
 | 
					  const [state, setState] = React.useState<State>(memoryState)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    listeners.push(setState)
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      const index = listeners.indexOf(setState)
 | 
				
			||||||
 | 
					      if (index > -1) {
 | 
				
			||||||
 | 
					        listeners.splice(index, 1)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [state])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    ...state,
 | 
				
			||||||
 | 
					    toast,
 | 
				
			||||||
 | 
					    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { useToast, toast }
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user