From 5b3272b2af186b7241b8de0ba6cbf9066eb2129f Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:53:22 +0530 Subject: [PATCH 01/38] Feat: Added the start registration-form --- package-lock.json | 137 ++++++++++++++++- package.json | 5 +- src/app/register/page.tsx | 10 ++ src/components/registration-form.tsx | 216 +++++++++++++++++++++++++++ src/components/ui/alert-dialog.tsx | 141 +++++++++++++++++ src/components/ui/alert.tsx | 59 ++++++++ src/components/ui/collapsible.tsx | 11 ++ src/components/ui/select.tsx | 164 ++++++++++++++++++++ 8 files changed, 741 insertions(+), 2 deletions(-) create mode 100644 src/app/register/page.tsx create mode 100644 src/components/registration-form.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/select.tsx diff --git a/package-lock.json b/package-lock.json index 21c9e00..eed84de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,15 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", "@prisma/client": "^5.19.1", + "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", "@react-email/components": "^0.0.25", @@ -36,7 +39,7 @@ "next-auth": "^4.24.7", "nodemailer": "^6.9.15", "otp-generator": "^4.0.1", - "react": "^18", + "react": "^18.3.1", "react-dom": "^18", "react-email": "^3.0.1", "resend": "^4.0.0", @@ -2253,11 +2256,45 @@ "@prisma/debug": "5.19.1" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.1.tgz", + "integrity": "sha512-wmCoJwj7byuVuiLKqDLlX7ClSUU0vd9sdCeM+2Ls+uf13+cpSJoMgwysHq1SGVVkJj5Xn0XWi1NoRCdkMpr6Mw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dialog": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -2334,6 +2371,36 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz", + "integrity": "sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -2713,10 +2780,54 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", + "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, @@ -2869,6 +2980,29 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", @@ -9677,6 +9811,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, diff --git a/package.json b/package.json index fb5f991..9df9d5f 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,15 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", "@prisma/client": "^5.19.1", + "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", + "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", "@react-email/components": "^0.0.25", @@ -39,7 +42,7 @@ "next-auth": "^4.24.7", "nodemailer": "^6.9.15", "otp-generator": "^4.0.1", - "react": "^18", + "react": "^18.3.1", "react-dom": "^18", "react-email": "^3.0.1", "resend": "^4.0.0", diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx new file mode 100644 index 0000000..1b22465 --- /dev/null +++ b/src/app/register/page.tsx @@ -0,0 +1,10 @@ +import RegistrationForm from '@/components/registration-form' +import React from 'react' + +export default function page() { + return ( +
+ +
+ ) +} diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx new file mode 100644 index 0000000..7eda8b5 --- /dev/null +++ b/src/components/registration-form.tsx @@ -0,0 +1,216 @@ +/** + * v0 by Vercel. + * @see https://v0.dev/t/wqxkG2U61F0 + * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app + */ +"use client" + +import { JSX, SetStateAction, SVGProps, useState } from "react" +import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible" +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" +import { Input } from "@/components/ui/input" +import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select" +import { AlertDialog, AlertDialogTrigger, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogContent, AlertDialogCancel, AlertDialogAction } from "@/components/ui/alert-dialog" + +export default function RegistrationForm() { + const [registrations, setRegistrations] = useState([ + { + name: "", + usn: "", + email: "", + contact: "", + designation: "", + photo: null, + collegeId: null, + }, + ]) + const addRegistration = () => { + setRegistrations([ + ...registrations, + { + name: "", + usn: "", + email: "", + contact: "", + designation: "", + photo: null, + collegeId: null, + }, + ]) + } + const updateRegistration = (index: number, field: string, value: string | File | null) => { + const updatedRegistrations = [...registrations] + // updatedRegistrations[index][field] = value + setRegistrations(updatedRegistrations) + } + const [showDeleteModal, setShowDeleteModal] = useState(false) + const [deleteIndex, setDeleteIndex] = useState(null) + const removeRegistration = (index: number | SetStateAction) => { + // setDeleteIndex(index) + // setShowDeleteModal(true) + } + const confirmDelete = () => { + if (deleteIndex !== null) { + const updatedRegistrations = [...registrations] + updatedRegistrations.splice(deleteIndex, 1) + setRegistrations(updatedRegistrations) + setShowDeleteModal(false) + } + } + const cancelDelete = () => { + setShowDeleteModal(false) + } + return ( +
+ {registrations.map((registration, index) => ( + + +
Registration {index + 1}
+
+ +
+
+ + +
+
+ + updateRegistration(index, "name", e.target.value)} + /> +
+
+ + updateRegistration(index, "usn", e.target.value)} + /> +
+
+
+
+ + updateRegistration(index, "email", e.target.value)} + /> +
+
+ + updateRegistration(index, "contact", e.target.value)} + /> +
+
+
+
+ + +
+
+ + updateRegistration(index, "photo", e.target.files?.[0] || null)} + /> +
+
+
+
+ + updateRegistration(index, "collegeId", e.target.files?.[0] || null)} + /> +
+
+
+ + ))} +
+ +
+ {showDeleteModal && ( + + +
+
+ + Are you sure? + + This action cannot be undone. This will permanently delete the registration. + + + + + Cancel + Delete + + +
+
+
+
+ )} +
+ ) +} + +function TrashIcon(props: JSX.IntrinsicAttributes & SVGProps) { + return ( + + + + + + ) +} \ No newline at end of file diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..57760f2 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...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, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx new file mode 100644 index 0000000..5afd41d --- /dev/null +++ b/src/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..9fa4894 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,11 @@ +"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 } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..ac2a8f2 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,164 @@ +"use client" + +import * as React from "react" +import { + CaretSortIcon, + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, +} from "@radix-ui/react-icons" +import * as SelectPrimitive from "@radix-ui/react-select" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} From 8f9c83e427f1318d2c93c8556ce2cb8c2f25ab1f Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:59:51 +0530 Subject: [PATCH 02/38] Feat: Add confirmation dialog component --- src/components/confirmation-dialog.tsx | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/components/confirmation-dialog.tsx diff --git a/src/components/confirmation-dialog.tsx b/src/components/confirmation-dialog.tsx new file mode 100644 index 0000000..192b3ec --- /dev/null +++ b/src/components/confirmation-dialog.tsx @@ -0,0 +1,43 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, + } from "@/components/ui/alert-dialog"; + import { Button } from "@/components/ui/button"; + + interface AlertDialogProps { + onConfirm: () => void; + title: string; + description: string; + } + + export function ConfirmationDialog({ + onConfirm, + title, + description, + }: AlertDialogProps) { + return ( + + + + + + + Are you absolutely sure? + {description} + + + Cancel + Continue + + + + ); + } + \ No newline at end of file From 30493f3d165a72853294aefce1bbf5db1a3c7253 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:42:04 +0530 Subject: [PATCH 03/38] Changed the form to an accordian and added additional styling --- package-lock.json | 32 +++ package.json | 1 + src/components/registration-form.tsx | 381 ++++++++++++++------------- src/components/ui/accordion.tsx | 57 ++++ tailwind.config.ts | 22 ++ 5 files changed, 313 insertions(+), 180 deletions(-) create mode 100644 src/components/ui/accordion.tsx diff --git a/package-lock.json b/package-lock.json index eed84de..922f191 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", "@prisma/client": "^5.19.1", + "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", @@ -2267,6 +2268,37 @@ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.0.tgz", + "integrity": "sha512-HJOzSX8dQqtsp/3jVxCU3CXEONF7/2jlGAB28oX8TTw1Dz8JYbEI1UcL8355PuLBE41/IRRMvCw7VkiK/jcUOQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collapsible": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.1.tgz", diff --git a/package.json b/package.json index 9df9d5f..9983989 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", "@prisma/client": "^5.19.1", + "@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.1", diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 7eda8b5..2205c27 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -1,17 +1,28 @@ -/** - * v0 by Vercel. - * @see https://v0.dev/t/wqxkG2U61F0 - * Documentation: https://v0.dev/docs#integrating-generated-code-into-your-nextjs-app - */ -"use client" +"use client"; -import { JSX, SetStateAction, SVGProps, useState } from "react" -import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@/components/ui/collapsible" -import { Button } from "@/components/ui/button" -import { Label } from "@/components/ui/label" -import { Input } from "@/components/ui/input" -import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select" -import { AlertDialog, AlertDialogTrigger, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogContent, AlertDialogCancel, AlertDialogAction } from "@/components/ui/alert-dialog" +import { JSX, SetStateAction, SVGProps, useState } from "react"; +import { + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from "@/components/ui/collapsible"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@/components/ui/select"; +import { ConfirmationDialog } from "./confirmation-dialog"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; export default function RegistrationForm() { const [registrations, setRegistrations] = useState([ @@ -21,10 +32,12 @@ export default function RegistrationForm() { email: "", contact: "", designation: "", + organization: "", + coupon: "", photo: null, collegeId: null, }, - ]) + ]); const addRegistration = () => { setRegistrations([ ...registrations, @@ -34,183 +47,191 @@ export default function RegistrationForm() { email: "", contact: "", designation: "", + organization: "", + coupon: "", photo: null, collegeId: null, }, - ]) - } - const updateRegistration = (index: number, field: string, value: string | File | null) => { - const updatedRegistrations = [...registrations] + ]); + }; + const updateRegistration = ( + index: number, + field: string, + value: string | File | null + ) => { + const updatedRegistrations = [...registrations]; // updatedRegistrations[index][field] = value - setRegistrations(updatedRegistrations) - } - const [showDeleteModal, setShowDeleteModal] = useState(false) - const [deleteIndex, setDeleteIndex] = useState(null) - const removeRegistration = (index: number | SetStateAction) => { - // setDeleteIndex(index) - // setShowDeleteModal(true) - } - const confirmDelete = () => { - if (deleteIndex !== null) { - const updatedRegistrations = [...registrations] - updatedRegistrations.splice(deleteIndex, 1) - setRegistrations(updatedRegistrations) - setShowDeleteModal(false) - } - } - const cancelDelete = () => { - setShowDeleteModal(false) - } + setRegistrations(updatedRegistrations); + }; + const removeRegistration = (index: number) => { + if (registrations.length === 1) return; + const updatedRegistrations = [...registrations]; + updatedRegistrations.splice(index, 1); + setRegistrations(updatedRegistrations); + }; + return ( -
+
{registrations.map((registration, index) => ( - - -
Registration {index + 1}
-
- -
-
- - -
-
- - updateRegistration(index, "name", e.target.value)} - /> -
-
- - updateRegistration(index, "usn", e.target.value)} - /> -
-
-
-
- - updateRegistration(index, "email", e.target.value)} - /> -
-
- - updateRegistration(index, "contact", e.target.value)} - /> -
-
-
-
- - -
-
- - updateRegistration(index, "photo", e.target.files?.[0] || null)} - /> -
-
-
-
- - updateRegistration(index, "collegeId", e.target.files?.[0] || null)} - /> -
-
-
- + + + +
Registration {index + 1}
+
+ +
+
+ + + updateRegistration(index, "name", e.target.value) + } + /> +
+
+ + + updateRegistration(index, "contact", e.target.value) + } + /> +
+
+
+
+ + + updateRegistration(index, "usn", e.target.value) + } + /> +
+
+ + +
+
+
+
+ + + updateRegistration(index, "email", e.target.value) + } + /> +
+
+ + + updateRegistration( + index, + "organization", + e.target.value + ) + } + /> +
+
+
+
+ + + updateRegistration(index, "coupon", e.target.value) + } + /> +
+
+ + + updateRegistration( + index, + "photo", + e.target.files?.[0] || null + ) + } + /> +
+
+
+
+ + + updateRegistration( + index, + "collegeId", + e.target.files?.[0] || null + ) + } + /> +
+
+
+
+
+
+ removeRegistration(index)} + title="Delete" + description="" + /> +
+
))}
- {showDeleteModal && ( - - -
-
- - Are you sure? - - This action cannot be undone. This will permanently delete the registration. - - - - - Cancel - Delete - - -
-
-
-
- )}
- ) + ); } - -function TrashIcon(props: JSX.IntrinsicAttributes & SVGProps) { - return ( - - - - - - ) -} \ No newline at end of file diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx new file mode 100644 index 0000000..8dcf9b6 --- /dev/null +++ b/src/components/ui/accordion.tsx @@ -0,0 +1,57 @@ +"use client" + +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "@radix-ui/react-icons" + +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 } diff --git a/tailwind.config.ts b/tailwind.config.ts index 0a9da95..7fa85bd 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -59,6 +59,28 @@ const config: Config = { '4': 'hsl(var(--chart-4))', '5': 'hsl(var(--chart-5))' } + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' } } }, From a49b31490462998c167513cc75a4e269a7289726 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:36:07 +0530 Subject: [PATCH 04/38] Refactor registration form styling and layout for the accordian and delete button --- src/components/registration-form.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 2205c27..881804e 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -79,7 +79,7 @@ export default function RegistrationForm() { > - +
Registration {index + 1}
@@ -220,7 +220,7 @@ export default function RegistrationForm() {
-
+
removeRegistration(index)} title="Delete" From 98fff3724730b9ef4edc8ab58004cecc97982b4c Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:41:53 +0530 Subject: [PATCH 05/38] Refactor: Added borders when the user open the accordion --- src/components/registration-form.tsx | 31 +++++++++++++--------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 881804e..ae8c7ff 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -1,28 +1,22 @@ "use client"; - -import { JSX, SetStateAction, SVGProps, useState } from "react"; import { - Collapsible, - CollapsibleTrigger, - CollapsibleContent, -} from "@/components/ui/collapsible"; + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Select, - SelectTrigger, - SelectValue, SelectContent, SelectItem, + SelectTrigger, + SelectValue, } from "@/components/ui/select"; +import { useState } from "react"; import { ConfirmationDialog } from "./confirmation-dialog"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; export default function RegistrationForm() { const [registrations, setRegistrations] = useState([ @@ -78,8 +72,11 @@ export default function RegistrationForm() { className=" relative flex justify-between items-center gap-4 w-full" > - - + +
Registration {index + 1}
From d20d4549158ac35c267822cd5dedad91fccc8f1e Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 21:53:29 +0530 Subject: [PATCH 06/38] Refactor: Rearranged the fields and prevent add registration when the previous form is not verified --- src/app/layout.tsx | 2 +- src/components/registration-form.tsx | 84 ++++++++++++++++------------ 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a0fd797..85fd7c5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -16,7 +16,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {children} diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index ae8c7ff..ba5da17 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -30,9 +30,12 @@ export default function RegistrationForm() { coupon: "", photo: null, collegeId: null, + verified: false, }, ]); const addRegistration = () => { + if (registrations.length === 5) return; + if (registrations[registrations.length - 1].verified === false) return; setRegistrations([ ...registrations, { @@ -45,6 +48,7 @@ export default function RegistrationForm() { coupon: "", photo: null, collegeId: null, + verified: false, }, ]); }; @@ -64,6 +68,11 @@ export default function RegistrationForm() { setRegistrations(updatedRegistrations); }; + const verifyRegistration = (index: number) => { + const updatedRegistrations = [...registrations]; + updatedRegistrations[index].verified = true; + setRegistrations(updatedRegistrations); + }; return (
{registrations.map((registration, index) => ( @@ -74,7 +83,7 @@ export default function RegistrationForm() {
Registration {index + 1}
@@ -93,29 +102,28 @@ export default function RegistrationForm() { />
- + - updateRegistration(index, "contact", e.target.value) + updateRegistration(index, "email", e.target.value) } />
- + - updateRegistration(index, "usn", e.target.value) + updateRegistration(index, "contact", e.target.value) } />
@@ -140,14 +148,15 @@ export default function RegistrationForm() {
- + - updateRegistration(index, "email", e.target.value) + updateRegistration(index, "usn", e.target.value) } />
@@ -171,13 +180,18 @@ export default function RegistrationForm() {
- + - updateRegistration(index, "coupon", e.target.value) + updateRegistration( + index, + "collegeId", + e.target.files?.[0] || null + ) } />
@@ -198,21 +212,21 @@ export default function RegistrationForm() {
- + - updateRegistration( - index, - "collegeId", - e.target.files?.[0] || null - ) + updateRegistration(index, "coupon", e.target.value) } />
+
+ +
From 49a8ed66019d784196f0a8880b665b72c496cadc Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:17:14 +0530 Subject: [PATCH 07/38] Feat: Added sonner toasts --- package-lock.json | 11 +++++++++ package.json | 1 + src/app/layout.tsx | 36 +++++++++++++++++++--------- src/components/registration-form.tsx | 2 ++ 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 922f191..1e853fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "react-dom": "^18", "react-email": "^3.0.1", "resend": "^4.0.0", + "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" @@ -10838,6 +10839,16 @@ "node": ">=10.0.0" } }, + "node_modules/sonner": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.5.0.tgz", + "integrity": "sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 9983989..37bbb64 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "react-dom": "^18", "react-email": "^3.0.1", "resend": "^4.0.0", + "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8" diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 85fd7c5..567947b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,24 +2,38 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import Providers from "@/components/Layout/Provider"; +import { Toaster } from "sonner"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Create Next App", + description: "Generated by create next app", }; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - return ( - - - {children} - - - ); + return ( + + + + {children}{" "} + + + + + ); } diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index ba5da17..671d723 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -17,6 +17,7 @@ import { } from "@/components/ui/select"; import { useState } from "react"; import { ConfirmationDialog } from "./confirmation-dialog"; +import { toast } from "sonner"; export default function RegistrationForm() { const [registrations, setRegistrations] = useState([ @@ -72,6 +73,7 @@ export default function RegistrationForm() { const updatedRegistrations = [...registrations]; updatedRegistrations[index].verified = true; setRegistrations(updatedRegistrations); + toast.success("Email verified successfully"); }; return (
From 267ebf4698f4870716a55651ac3e5c1db5933c5f Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Sat, 21 Sep 2024 20:26:25 +0530 Subject: [PATCH 08/38] Refactor: Update registration form to disable certain fields based on designation --- src/components/registration-form.tsx | 47 +++++++++++----------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 671d723..ecdda8d 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -20,38 +20,24 @@ import { ConfirmationDialog } from "./confirmation-dialog"; import { toast } from "sonner"; export default function RegistrationForm() { - const [registrations, setRegistrations] = useState([ - { - name: "", - usn: "", - email: "", - contact: "", - designation: "", - organization: "", - coupon: "", - photo: null, - collegeId: null, - verified: false, - }, - ]); + const initialRegistration = { + name: "", + usn: "", + email: "", + contact: "", + designation: "", + organization: "", + coupon: "", + photo: null, + collegeId: null, + verified: false, + }; + + const [registrations, setRegistrations] = useState([initialRegistration]); const addRegistration = () => { if (registrations.length === 5) return; if (registrations[registrations.length - 1].verified === false) return; - setRegistrations([ - ...registrations, - { - name: "", - usn: "", - email: "", - contact: "", - designation: "", - organization: "", - coupon: "", - photo: null, - collegeId: null, - verified: false, - }, - ]); + setRegistrations([...registrations, initialRegistration]); }; const updateRegistration = ( index: number, @@ -160,6 +146,7 @@ export default function RegistrationForm() { onChange={(e) => updateRegistration(index, "usn", e.target.value) } + disabled={registration.designation !== "student"} />
@@ -177,6 +164,7 @@ export default function RegistrationForm() { e.target.value ) } + />
@@ -195,6 +183,7 @@ export default function RegistrationForm() { e.target.files?.[0] || null ) } + disabled={registration.designation !== "student"} />
From 074e3edab1247697e16d1a2b0d8ae4a00ffadd69 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Sat, 21 Sep 2024 22:46:53 +0530 Subject: [PATCH 09/38] Refactor: Added type to registration and fixed the details entry --- src/components/registration-form.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index ecdda8d..9839c93 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -25,7 +25,7 @@ export default function RegistrationForm() { usn: "", email: "", contact: "", - designation: "", + designation: "student", organization: "", coupon: "", photo: null, @@ -39,13 +39,13 @@ export default function RegistrationForm() { if (registrations[registrations.length - 1].verified === false) return; setRegistrations([...registrations, initialRegistration]); }; - const updateRegistration = ( + const updateRegistration = ( index: number, - field: string, - value: string | File | null + field: K, + value: typeof initialRegistration[K] | File | null ) => { const updatedRegistrations = [...registrations]; - // updatedRegistrations[index][field] = value + updatedRegistrations[index][field] = value as typeof initialRegistration[K]; setRegistrations(updatedRegistrations); }; const removeRegistration = (index: number) => { @@ -119,9 +119,13 @@ export default function RegistrationForm() { - updateRegistration(index, "name", e.target.value) - } - /> -
-
- - - updateRegistration(index, "email", e.target.value) - } - /> -
-
-
-
- - - updateRegistration(index, "contact", e.target.value) - } - /> -
-
- - -
-
-
-
- - - updateRegistration(index, "usn", e.target.value) - } - disabled={registration.designation !== "student"} - /> -
-
- - - updateRegistration( - index, - "organization", - e.target.value - ) - } - - /> -
-
-
-
- - - updateRegistration( - index, - "collegeId", - e.target.files?.[0] || null - ) - } - disabled={registration.designation !== "student"} - /> -
-
- - - updateRegistration( - index, - "photo", - e.target.files?.[0] || null - ) - } - /> -
-
-
-
- - - updateRegistration(index, "coupon", e.target.value) - } - /> -
-
- -
-
- - - -
- removeRegistration(index)} - title="Delete" - description="" - /> -
+
+
+ + updateRegistration("name", e.target.value)} + /> +
+
+ + updateRegistration("email", e.target.value)} + /> +
+
+
+
+ + updateRegistration("contact", e.target.value)} + /> +
+
+ + +
+
+
+
+ + updateRegistration("usn", e.target.value)} + disabled={registration.designation !== "student"} + /> +
+
+ + updateRegistration("organization", e.target.value)} + /> +
+
+
+
+ + + updateRegistration( + "collegeId", + (e.target.files?.[0] as File) || null + ) + } + disabled={registration.designation !== "student"} + /> +
+
+ + + updateRegistration("photo", e.target.files?.[0] || null) + } + /> +
+
+
+
+ + updateRegistration("coupon", e.target.value)} + /> +
+
+
- ))} -
-
); From 6efd172ba8fa5549546a5c076af0843b3799b3fa Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:05:30 +0530 Subject: [PATCH 12/38] Feat: Added upload thing to upload images --- package-lock.json | 202 +++++++++++++++++++++++++++ package.json | 2 + src/app/api/uploadthing/core.ts | 34 +++++ src/app/api/uploadthing/route.ts | 11 ++ src/app/register/page.tsx | 1 + src/components/registration-form.tsx | 13 ++ src/utils/uploadthing.ts | 10 ++ 7 files changed, 273 insertions(+) create mode 100644 src/app/api/uploadthing/core.ts create mode 100644 src/app/api/uploadthing/route.ts create mode 100644 src/utils/uploadthing.ts diff --git a/package-lock.json b/package-lock.json index 1e853fe..61dc995 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@tanstack/react-query": "^5.56.2", "@types/ioredis": "^4.28.10", "@types/lodash.debounce": "^4.0.9", + "@uploadthing/react": "^7.0.2", "axios": "^1.7.7", "bullmq": "^5.13.0", "class-variance-authority": "^0.7.0", @@ -47,6 +48,7 @@ "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", + "uploadthing": "^7.0.2", "zod": "^3.23.8" }, "devDependencies": { @@ -704,6 +706,32 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, + "node_modules/@effect/platform": { + "version": "0.63.2", + "resolved": "https://registry.npmjs.org/@effect/platform/-/platform-0.63.2.tgz", + "integrity": "sha512-b39pVFw0NGo/tXjGShW7Yg0M+kG7bRrFR6+dQ3aIu99ePTkTp6bGb/kDB7n+dXsFFdIqHsQGYESeYcOQngxdFQ==", + "license": "MIT", + "dependencies": { + "find-my-way-ts": "^0.1.5", + "multipasta": "^0.2.5" + }, + "peerDependencies": { + "@effect/schema": "^0.72.2", + "effect": "^3.7.2" + } + }, + "node_modules/@effect/schema": { + "version": "0.72.2", + "resolved": "https://registry.npmjs.org/@effect/schema/-/schema-0.72.2.tgz", + "integrity": "sha512-/x1BIA2pqcUidNrOMmwYe6Z58KtSgHSc5iJu7bNwIxi2LHMVuUao1BvpI5x6i7T/zkoi4dd1S6qasZzJIYDjdw==", + "license": "MIT", + "dependencies": { + "fast-check": "^3.21.0" + }, + "peerDependencies": { + "effect": "^3.7.2" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz", @@ -3756,6 +3784,72 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@uploadthing/dropzone": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@uploadthing/dropzone/-/dropzone-0.4.1.tgz", + "integrity": "sha512-RHSpo/2kg/mrRSYQA4EKlyvkOCYWOeE2+QQYW9YiUvWCuawnTfD7DQvk8RN/nYXi1Sw7/v0NegmQpiVELVGtnA==", + "license": "MIT", + "dependencies": { + "file-selector": "^0.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "solid-js": "^1.7.11", + "svelte": "^4.2.12", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@uploadthing/mime-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@uploadthing/mime-types/-/mime-types-0.3.0.tgz", + "integrity": "sha512-jN/XFvpKTzcd3MXT/9D9oxx05scnYiSYxAXF/e6hyg377zFducRxivU/kHyYTkpUZPTmOL5q9EQbOkUsXMlSMg==", + "license": "MIT" + }, + "node_modules/@uploadthing/react": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@uploadthing/react/-/react-7.0.2.tgz", + "integrity": "sha512-343zTofWBo1yR+31/oP75WCTCC1lUuyAhfoqgJ0MEY64i15KoQAaTe3ICSNnXyeRaHCmJSQeK3hbEl44QGq/iQ==", + "license": "MIT", + "dependencies": { + "@uploadthing/dropzone": "0.4.1", + "@uploadthing/shared": "7.0.2" + }, + "peerDependencies": { + "next": "*", + "react": "^17.0.2 || ^18.0.0", + "uploadthing": "7.0.2" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + } + } + }, + "node_modules/@uploadthing/shared": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@uploadthing/shared/-/shared-7.0.2.tgz", + "integrity": "sha512-yhE7lA42m6g7Qw245Ey/8uK5J4d8FJhOg90VVt0PG1iJYvBZHbboSq1ndsGZ1X8jFaskBVORzWc66gEw43FEsQ==", + "license": "MIT", + "dependencies": { + "@uploadthing/mime-types": "0.3.0", + "effect": "3.7.2", + "sqids": "^0.3.0" + } + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -5262,6 +5356,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/effect": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.7.2.tgz", + "integrity": "sha512-pV7l1+LSZFvVObj4zuy4nYiBaC7qZOfrKV6s/Ef4p3KueiQwZFgamazklwyZ+x7Nyj2etRDFvHE/xkThTfQD1w==", + "license": "MIT" + }, + "node_modules/effect-log": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/effect-log/-/effect-log-0.32.0.tgz", + "integrity": "sha512-zlh4S+zBkHeDhiV5IAAXwecqxASVJk9tYNJUb12EuJqgtRGGyhrXYNn8zz5Gk/w7PmnNLTl9Vb6bQo2BFn+J/Q==", + "license": "MIT", + "peerDependencies": { + "effect": "^3.7.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.19", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz", @@ -6094,6 +6203,28 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/fast-check": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.22.0.tgz", + "integrity": "sha512-8HKz3qXqnHYp/VCNn2qfjHdAdcI8zcSqOyX64GOMukp7SL2bfzfeDKjSd+UyECtejccaZv3LcvZTm9YDD22iCQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6189,6 +6320,18 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6200,6 +6343,12 @@ "node": ">=8" } }, + "node_modules/find-my-way-ts": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.5.tgz", + "integrity": "sha512-4GOTMrpGQVzsCH2ruUn2vmwzV/02zF4q+ybhCIrw/Rkt3L8KWcycdC6aJMctJzwN4fXD4SD5F/4B9Sksh5rE0A==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8777,6 +8926,12 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" } }, + "node_modules/multipasta": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.5.tgz", + "integrity": "sha512-c8eMDb1WwZcE02WVjHoOmUVk7fnKU/RmUcosHACglrWAuPQsEJv+E8430sXj6jNc1jHw0zrS16aCjQh4BcEb4A==", + "license": "MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -10882,6 +11037,12 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, + "node_modules/sqids": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/sqids/-/sqids-0.3.0.tgz", + "integrity": "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==", + "license": "MIT" + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -11574,6 +11735,47 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uploadthing": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/uploadthing/-/uploadthing-7.0.2.tgz", + "integrity": "sha512-B5/r0nmOfWjo+cGvZLyDQ8jypJYWoW/VsmoL+VqkiGMg5yIkKzMGJ5InrDOJS+3WQBbW8KdhVrRyA+mGSZEGUw==", + "license": "MIT", + "dependencies": { + "@effect/platform": "0.63.2", + "@effect/schema": "0.72.2", + "@uploadthing/mime-types": "0.3.0", + "@uploadthing/shared": "7.0.2", + "effect": "3.7.2", + "effect-log": "0.32.0" + }, + "engines": { + "node": ">=18.13.0" + }, + "peerDependencies": { + "express": "*", + "fastify": "*", + "h3": "*", + "next": "*", + "tailwindcss": "*" + }, + "peerDependenciesMeta": { + "express": { + "optional": true + }, + "fastify": { + "optional": true + }, + "h3": { + "optional": true + }, + "next": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 37bbb64..4b64375 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@tanstack/react-query": "^5.56.2", "@types/ioredis": "^4.28.10", "@types/lodash.debounce": "^4.0.9", + "@uploadthing/react": "^7.0.2", "axios": "^1.7.7", "bullmq": "^5.13.0", "class-variance-authority": "^0.7.0", @@ -50,6 +51,7 @@ "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", + "uploadthing": "^7.0.2", "zod": "^3.23.8" }, "devDependencies": { diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts new file mode 100644 index 0000000..6b36404 --- /dev/null +++ b/src/app/api/uploadthing/core.ts @@ -0,0 +1,34 @@ +import { createUploadthing, type FileRouter } from "uploadthing/next"; +import { UploadThingError } from "uploadthing/server"; + +const f = createUploadthing(); + +const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function + +// FileRouter for your app, can contain multiple FileRoutes +export const ourFileRouter = { + // Define as many FileRoutes as you like, each with a unique routeSlug + imageUploader: f({ image: { maxFileSize: "4MB" } }) + // Set permissions and file types for this FileRoute + .middleware(async ({ req }) => { + // This code runs on your server before upload + const user = await auth(req); + + // If you throw, the user will not be able to upload + if (!user) throw new UploadThingError("Unauthorized"); + + // Whatever is returned here is accessible in onUploadComplete as `metadata` + return { userId: user.id }; + }) + .onUploadComplete(async ({ metadata, file }) => { + // This code RUNS ON YOUR SERVER after upload + console.log("Upload complete for userId:", metadata.userId); + + console.log("file url", file.url); + + // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback + return { uploadedBy: metadata.userId }; + }), +} satisfies FileRouter; + +export type OurFileRouter = typeof ourFileRouter; diff --git a/src/app/api/uploadthing/route.ts b/src/app/api/uploadthing/route.ts new file mode 100644 index 0000000..81af864 --- /dev/null +++ b/src/app/api/uploadthing/route.ts @@ -0,0 +1,11 @@ +import { createRouteHandler } from "uploadthing/next"; + +import { ourFileRouter } from "./core"; + +// Export routes for Next App Router +export const { GET, POST } = createRouteHandler({ + router: ourFileRouter, + + // Apply an (optional) custom config: + // config: { ... }, +}); diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 1b22465..caba8d0 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -5,6 +5,7 @@ export default function page() { return (
+
) } diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 83ae225..c10f168 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -11,6 +11,7 @@ import { } from "@/components/ui/select"; import { useState } from "react"; import { toast } from "sonner"; +import { UploadButton } from "@/utils/uploadthing"; export default function RegistrationForm() { const initialRegistration = { @@ -149,6 +150,18 @@ export default function RegistrationForm() {
+ { + // Do something with the response + console.log("Files: ", res); + alert("Upload Completed"); + }} + onUploadError={(error: Error) => { + // Do something with the error. + alert(`ERROR! ${error.message}`); + }} + />
); diff --git a/src/utils/uploadthing.ts b/src/utils/uploadthing.ts new file mode 100644 index 0000000..ebe0a10 --- /dev/null +++ b/src/utils/uploadthing.ts @@ -0,0 +1,10 @@ +import { + generateUploadButton, + generateUploadDropzone, + } from "@uploadthing/react"; + + import type { OurFileRouter } from "@/app/api/uploadthing/core"; + + export const UploadButton = generateUploadButton(); + export const UploadDropzone = generateUploadDropzone(); + \ No newline at end of file From b183c23173dd487e3f486434a1c8a67dba8e0bae Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:02:34 +0530 Subject: [PATCH 13/38] added auth() to get user from session --- src/lib/utils.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bd0c391..b092f5c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,14 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; +import { getServerSideSession } from "./get-server-session"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); +} + +export async function auth() { + const session = await getServerSideSession(); + if (session && session.user) { + return session.user; + } else return null; } From 8958976c896add13ecbc6e4af87507e448218c68 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:41:50 +0530 Subject: [PATCH 14/38] Refactor: Update auth function to get user from session --- src/app/api/uploadthing/core.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index 6b36404..7ae38f6 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -1,27 +1,27 @@ +import { getServerSideSession } from "@/lib/get-server-session"; import { createUploadthing, type FileRouter } from "uploadthing/next"; import { UploadThingError } from "uploadthing/server"; const f = createUploadthing(); -const auth = (req: Request) => ({ id: "fakeId" }); // Fake auth function +const auth = async (req: Request) => { + const session = await getServerSideSession(); + if (session && session.user) { + return session.user; + } else return null; +}; -// FileRouter for your app, can contain multiple FileRoutes export const ourFileRouter = { - // Define as many FileRoutes as you like, each with a unique routeSlug imageUploader: f({ image: { maxFileSize: "4MB" } }) - // Set permissions and file types for this FileRoute .middleware(async ({ req }) => { - // This code runs on your server before upload + console.log("Middleware for imageUploader", req.url); const user = await auth(req); - // If you throw, the user will not be able to upload if (!user) throw new UploadThingError("Unauthorized"); - // Whatever is returned here is accessible in onUploadComplete as `metadata` return { userId: user.id }; }) .onUploadComplete(async ({ metadata, file }) => { - // This code RUNS ON YOUR SERVER after upload console.log("Upload complete for userId:", metadata.userId); console.log("file url", file.url); From 8917b3f616fb7e3feaa90d72cc5977a6a6c798a0 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:03:35 +0530 Subject: [PATCH 15/38] fix: remove auth() function in utils which was causing the "url undefined aka env undefined" --- src/lib/utils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b092f5c..f363231 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,9 +6,4 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export async function auth() { - const session = await getServerSideSession(); - if (session && session.user) { - return session.user; - } else return null; -} + From 22091a29fb3d41733599a1366a73db7ed39846d5 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:34:52 +0530 Subject: [PATCH 16/38] Added ssr plugin for Improving SSR for upload thing --- src/app/layout.tsx | 11 +++++++++-- src/components/registration-form.tsx | 5 ++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 567947b..1c04f7d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,12 @@ +import Providers from "@/components/Layout/Provider"; +import { NextSSRPlugin } from "@uploadthing/react/next-ssr-plugin"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; -import "./globals.css"; -import Providers from "@/components/Layout/Provider"; import { Toaster } from "sonner"; +import { extractRouterConfig } from "uploadthing/server"; +import "./globals.css"; + +import { ourFileRouter } from "@/app/api/uploadthing/core"; const inter = Inter({ subsets: ["latin"] }); @@ -19,6 +23,9 @@ export default function RootLayout({ return ( + {children}{" "} { - // Do something with the response console.log("Files: ", res); - alert("Upload Completed"); + toast.success("Upload Completed"); }} onUploadError={(error: Error) => { // Do something with the error. - alert(`ERROR! ${error.message}`); + toast.error(`ERROR! ${error.message}`); }} />
From 95341922d68e44cf018d068818c2bd4ae8c25b87 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:38:42 +0530 Subject: [PATCH 17/38] Add styles for upload thing in registration form --- src/components/registration-form.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 96fa739..0ccad9f 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -12,6 +12,7 @@ import { import { useState } from "react"; import { toast } from "sonner"; import { UploadButton } from "@/utils/uploadthing"; +import "@uploadthing/react/styles.css"; export default function RegistrationForm() { const initialRegistration = { From 78e24acea913b32e538d6d88b3769d6c1ddc9eb1 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:59:55 +0530 Subject: [PATCH 18/38] added custom uploadthing function using hook --- src/components/registration-form.tsx | 37 +++++++++++++++++----------- src/lib/utils.ts | 1 - src/utils/uploadthing.ts | 21 +++++++++------- 3 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 0ccad9f..79455f8 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -1,4 +1,4 @@ -"use client";; +"use client"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -13,6 +13,7 @@ import { useState } from "react"; import { toast } from "sonner"; import { UploadButton } from "@/utils/uploadthing"; import "@uploadthing/react/styles.css"; +import { useUploadThing } from "@/utils/uploadthing"; export default function RegistrationForm() { const initialRegistration = { @@ -36,6 +37,19 @@ export default function RegistrationForm() { const verifyRegistration = () => { toast.success("Email verified successfully"); }; + + const { startUpload, routeConfig } = useUploadThing("imageUploader", { + onClientUploadComplete: () => { + alert("uploaded successfully!"); + }, + onUploadError: () => { + alert("error occurred while uploading"); + }, + onUploadBegin: (file: string) => { + console.log("upload has begun for", file); + }, + }); + return (
@@ -132,9 +146,13 @@ export default function RegistrationForm() { - updateRegistration("photo", e.target.files?.[0] || null) - } + onChange={(e) => { + const file = e.target.files?.[0] || null; + if (file) { + startUpload([file]); + } + updateRegistration("photo", file); + }} />
@@ -151,17 +169,6 @@ export default function RegistrationForm() {
- { - console.log("Files: ", res); - toast.success("Upload Completed"); - }} - onUploadError={(error: Error) => { - // Do something with the error. - toast.error(`ERROR! ${error.message}`); - }} - />
); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index f363231..193828b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,5 @@ import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; -import { getServerSideSession } from "./get-server-session"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); diff --git a/src/utils/uploadthing.ts b/src/utils/uploadthing.ts index ebe0a10..d6821c4 100644 --- a/src/utils/uploadthing.ts +++ b/src/utils/uploadthing.ts @@ -1,10 +1,13 @@ import { - generateUploadButton, - generateUploadDropzone, - } from "@uploadthing/react"; - - import type { OurFileRouter } from "@/app/api/uploadthing/core"; - - export const UploadButton = generateUploadButton(); - export const UploadDropzone = generateUploadDropzone(); - \ No newline at end of file + generateUploadButton, + generateUploadDropzone, +} from "@uploadthing/react"; + +import type { OurFileRouter } from "@/app/api/uploadthing/core"; +import { generateReactHelpers } from "@uploadthing/react"; + +export const { useUploadThing, uploadFiles } = + generateReactHelpers(); + +export const UploadButton = generateUploadButton(); +export const UploadDropzone = generateUploadDropzone(); From 325def35609b63b401169c495e96282928633dd9 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:44:43 +0530 Subject: [PATCH 19/38] added loading and completed animation --- src/app/api/uploadthing/core.ts | 4 ++-- src/components/registration-form.tsx | 35 ++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index 7ae38f6..b0c641e 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -22,9 +22,9 @@ export const ourFileRouter = { return { userId: user.id }; }) .onUploadComplete(async ({ metadata, file }) => { - console.log("Upload complete for userId:", metadata.userId); + // console.log("Upload complete for userId:", metadata.userId); - console.log("file url", file.url); + // console.log("file url", file.url); // !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback return { uploadedBy: metadata.userId }; diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 79455f8..97d0d79 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -11,9 +11,9 @@ import { } from "@/components/ui/select"; import { useState } from "react"; import { toast } from "sonner"; -import { UploadButton } from "@/utils/uploadthing"; import "@uploadthing/react/styles.css"; import { useUploadThing } from "@/utils/uploadthing"; +import { CheckCircle, Loader } from "lucide-react"; export default function RegistrationForm() { const initialRegistration = { @@ -28,6 +28,8 @@ export default function RegistrationForm() { collegeId: null, verified: false, }; + const [isPhotoUploading, setIsPhotoUploading] = useState(false); + const [isPhotoUploaded, setIsPhotoUploaded] = useState(false); const [registration, setRegistration] = useState(initialRegistration); const updateRegistration = (key: string, value: string | File | null) => { @@ -39,13 +41,16 @@ export default function RegistrationForm() { }; const { startUpload, routeConfig } = useUploadThing("imageUploader", { - onClientUploadComplete: () => { - alert("uploaded successfully!"); + onClientUploadComplete: (res) => { + console.log("Client upload complete, response:", res[0].url); + setIsPhotoUploading(false); + setIsPhotoUploaded(true); }, onUploadError: () => { - alert("error occurred while uploading"); + toast.error("Upload failed"); }, onUploadBegin: (file: string) => { + setIsPhotoUploading(true); console.log("upload has begun for", file); }, }); @@ -132,28 +137,37 @@ export default function RegistrationForm() { + onChange={(e) => { + const file = e.target.files?.[0] || null; + if (file) { + startUpload([file]); + } updateRegistration( "collegeId", (e.target.files?.[0] as File) || null - ) - } + ); + }} disabled={registration.designation !== "student"} /> -
- +
{ const file = e.target.files?.[0] || null; if (file) { - startUpload([file]); + setIsPhotoUploading(true); + startUpload([file]).then(() => { + setIsPhotoUploading(false); + setIsPhotoUploaded(true); + }); } updateRegistration("photo", file); }} /> + {isPhotoUploading && } + {isPhotoUploaded && }
@@ -169,6 +183,7 @@ export default function RegistrationForm() {
+
); From 8fc8786f9ff1ced771a6a459b7bbbbf5f2a36507 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:59:22 +0530 Subject: [PATCH 20/38] Feat: uploading collegeid and photo when i click on register --- src/components/registration-form.tsx | 46 ++++++++++------------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index 97d0d79..c8627ae 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -9,11 +9,10 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { useUploadThing } from "@/utils/uploadthing"; +import "@uploadthing/react/styles.css"; import { useState } from "react"; import { toast } from "sonner"; -import "@uploadthing/react/styles.css"; -import { useUploadThing } from "@/utils/uploadthing"; -import { CheckCircle, Loader } from "lucide-react"; export default function RegistrationForm() { const initialRegistration = { @@ -28,29 +27,34 @@ export default function RegistrationForm() { collegeId: null, verified: false, }; - const [isPhotoUploading, setIsPhotoUploading] = useState(false); - const [isPhotoUploaded, setIsPhotoUploaded] = useState(false); + const [selectedPhoto, setSelectedPhoto] = useState(null); + const [selectedCollegeId, setSelectedCollegeId] = useState(null); const [registration, setRegistration] = useState(initialRegistration); const updateRegistration = (key: string, value: string | File | null) => { setRegistration({ ...registration, [key]: value }); }; - const verifyRegistration = () => { - toast.success("Email verified successfully"); + const handleRegister = async () => { + if (selectedCollegeId) { + await startUpload([selectedCollegeId]); + toast.success("college id uploaded"); + } + if (selectedPhoto) { + await startUpload([selectedPhoto]); + toast.success("photo uploaded"); + } + toast.success("Registered successfully"); }; const { startUpload, routeConfig } = useUploadThing("imageUploader", { onClientUploadComplete: (res) => { console.log("Client upload complete, response:", res[0].url); - setIsPhotoUploading(false); - setIsPhotoUploaded(true); }, onUploadError: () => { toast.error("Upload failed"); }, onUploadBegin: (file: string) => { - setIsPhotoUploading(true); console.log("upload has begun for", file); }, }); @@ -139,13 +143,7 @@ export default function RegistrationForm() { type="file" onChange={(e) => { const file = e.target.files?.[0] || null; - if (file) { - startUpload([file]); - } - updateRegistration( - "collegeId", - (e.target.files?.[0] as File) || null - ); + setSelectedCollegeId(file); }} disabled={registration.designation !== "student"} /> @@ -156,18 +154,9 @@ export default function RegistrationForm() { type="file" onChange={(e) => { const file = e.target.files?.[0] || null; - if (file) { - setIsPhotoUploading(true); - startUpload([file]).then(() => { - setIsPhotoUploading(false); - setIsPhotoUploaded(true); - }); - } - updateRegistration("photo", file); + setSelectedPhoto(file); }} /> - {isPhotoUploading && } - {isPhotoUploaded && }
@@ -181,9 +170,8 @@ export default function RegistrationForm() { />
- +
- ); From 31fe43433dc0dd690367393437e5839462c2c9f5 Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:11:56 +0530 Subject: [PATCH 21/38] Refactor: Improve registration form and upload functionality --- src/components/registration-form.tsx | 35 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index c8627ae..ecbe2c3 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -27,26 +27,18 @@ export default function RegistrationForm() { collegeId: null, verified: false, }; + + const [registration, setRegistration] = useState(initialRegistration); + const [selectedPhoto, setSelectedPhoto] = useState(null); const [selectedCollegeId, setSelectedCollegeId] = useState(null); + const [uploading, setUploading] = useState(false); + - const [registration, setRegistration] = useState(initialRegistration); const updateRegistration = (key: string, value: string | File | null) => { setRegistration({ ...registration, [key]: value }); }; - const handleRegister = async () => { - if (selectedCollegeId) { - await startUpload([selectedCollegeId]); - toast.success("college id uploaded"); - } - if (selectedPhoto) { - await startUpload([selectedPhoto]); - toast.success("photo uploaded"); - } - toast.success("Registered successfully"); - }; - const { startUpload, routeConfig } = useUploadThing("imageUploader", { onClientUploadComplete: (res) => { console.log("Client upload complete, response:", res[0].url); @@ -58,6 +50,19 @@ export default function RegistrationForm() { console.log("upload has begun for", file); }, }); + const handleRegister = async () => { + setUploading(true); + if (selectedCollegeId) { + await startUpload([selectedCollegeId]); + toast.success("college id uploaded"); + } + if (selectedPhoto) { + await startUpload([selectedPhoto]); + toast.success("photo uploaded"); + } + toast.success("Registered successfully"); + setUploading(false); + }; return (
@@ -170,7 +175,9 @@ export default function RegistrationForm() { />
- +
From 3f84facadc5ae620b33f180fe9f167f610a574bc Mon Sep 17 00:00:00 2001 From: Joywin Bennis <107112207+joywin2003@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:39:00 +0530 Subject: [PATCH 22/38] Refactor: Add @hookform/resolvers and react-hook-form package for improved form functionality --- package-lock.json | 28 +++ package.json | 2 + src/components/registration-form.tsx | 312 ++++++++++++++++----------- src/components/ui/form.tsx | 178 +++++++++++++++ 4 files changed, 395 insertions(+), 125 deletions(-) create mode 100644 src/components/ui/form.tsx diff --git a/package-lock.json b/package-lock.json index 61dc995..3d00fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "tedx", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^3.9.0", "@libsql/client": "^0.8.1", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", @@ -44,6 +45,7 @@ "react": "^18.3.1", "react-dom": "^18", "react-email": "^3.0.1", + "react-hook-form": "^7.53.0", "resend": "^4.0.0", "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", @@ -1190,6 +1192,15 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==" }, + "node_modules/@hookform/resolvers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", + "integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2658,6 +2669,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.0.0" }, @@ -10393,6 +10405,22 @@ "semver": "bin/semver.js" } }, + "node_modules/react-hook-form": { + "version": "7.53.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz", + "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 4b64375..7606a45 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "migrate-latest": "export $(grep -v '^#' .env | xargs) && LATEST_MIGRATION=$(find prisma/migrations/*/ -type d -name '[0-9]*_*' | sort -r | head -n 1) && turso db shell $TURSO_DB_NAME < ${LATEST_MIGRATION}migration.sql" }, "dependencies": { + "@hookform/resolvers": "^3.9.0", "@libsql/client": "^0.8.1", "@next-auth/prisma-adapter": "^1.0.7", "@prisma/adapter-libsql": "^5.19.1", @@ -47,6 +48,7 @@ "react": "^18.3.1", "react-dom": "^18", "react-email": "^3.0.1", + "react-hook-form": "^7.53.0", "resend": "^4.0.0", "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", diff --git a/src/components/registration-form.tsx b/src/components/registration-form.tsx index ecbe2c3..5ddd617 100644 --- a/src/components/registration-form.tsx +++ b/src/components/registration-form.tsx @@ -1,7 +1,6 @@ "use client"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Select, SelectContent, @@ -12,32 +11,25 @@ import { import { useUploadThing } from "@/utils/uploadthing"; import "@uploadthing/react/styles.css"; import { useState } from "react"; +import { useForm } from "react-hook-form"; import { toast } from "sonner"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; export default function RegistrationForm() { - const initialRegistration = { - name: "", - usn: "", - email: "", - contact: "", - designation: "student", - organization: "", - coupon: "", - photo: null, - collegeId: null, - verified: false, - }; + const form = useForm(); - const [registration, setRegistration] = useState(initialRegistration); - const [selectedPhoto, setSelectedPhoto] = useState(null); const [selectedCollegeId, setSelectedCollegeId] = useState(null); const [uploading, setUploading] = useState(false); - - - const updateRegistration = (key: string, value: string | File | null) => { - setRegistration({ ...registration, [key]: value }); - }; + const [designation, setDesignation] = useState(undefined); const { startUpload, routeConfig } = useUploadThing("imageUploader", { onClientUploadComplete: (res) => { @@ -50,136 +42,206 @@ export default function RegistrationForm() { console.log("upload has begun for", file); }, }); + const handleRegister = async () => { setUploading(true); if (selectedCollegeId) { await startUpload([selectedCollegeId]); - toast.success("college id uploaded"); + toast.success("College ID uploaded"); } if (selectedPhoto) { await startUpload([selectedPhoto]); - toast.success("photo uploaded"); + toast.success("Photo uploaded"); } toast.success("Registered successfully"); setUploading(false); }; return ( -
-
-
- - updateRegistration("name", e.target.value)} +
+ +
+ ( + + Name + + + + + This is your public display name. + + + + )} /> -
-
- - updateRegistration("email", e.target.value)} + ( + + Email + + + + + For an additional discount, please use your SJEC email ID. + + + + )} />
-
-
-
- - updateRegistration("contact", e.target.value)} +
+ ( + + Contact + + + + + )} /> -
-
- - -
-
-
-
- - updateRegistration("usn", e.target.value)} - disabled={registration.designation !== "student"} + ( + + Designation + + + )} />
-
- - updateRegistration("organization", e.target.value)} +
+ ( + + + USN (University Seat Number) + + + + + + )} /> -
-
-
-
- - { - const file = e.target.files?.[0] || null; - setSelectedCollegeId(file); - }} - disabled={registration.designation !== "student"} + ( + + + Organization/College + + + + + + )} />
-
- { - const file = e.target.files?.[0] || null; - setSelectedPhoto(file); - }} +
+ ( + + College ID Card + + { + const file = e.target.files?.[0] || null; + setSelectedCollegeId(file); + }} + disabled={designation !== "student"} + /> + + + )} /> -
-
-
-
- - updateRegistration("coupon", e.target.value)} + ( + + Photo + + { + const file = e.target.files?.[0] || null; + setSelectedPhoto(file); + }} + /> + + + )} />
-
- +
+ ( + + Coupon + + + + + )} + /> +
+ +
-
-
+ + ); } diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 0000000..b6daa65 --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,178 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +const Form = FormProvider + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +) + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ) +} + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext) + const itemContext = React.useContext(FormItemContext) + const { getFieldState, formState } = useFormContext() + + const fieldState = getFieldState(fieldContext.name, formState) + + if (!fieldContext) { + throw new Error("useFormField should be used within ") + } + + const { id } = itemContext + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} + +type FormItemContextValue = { + id: string +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +