From 19ae57d449a4bba99ac62e16fb5660b4bd1ceb82 Mon Sep 17 00:00:00 2001 From: NexVeridian Date: Thu, 15 Feb 2024 14:07:36 -0800 Subject: [PATCH] create --- package-lock.json | 68 ++++++++++++-- package.json | 8 +- src/app/create/page.tsx | 71 +++++++++++++++ src/app/page.tsx | 28 +++++- src/components/Nav.tsx | 15 ++- src/components/ui/form.tsx | 176 ++++++++++++++++++++++++++++++++++++ src/components/ui/input.tsx | 25 +++++ src/components/ui/label.tsx | 24 +++++ 8 files changed, 396 insertions(+), 19 deletions(-) create mode 100644 src/app/create/page.tsx create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx diff --git a/package-lock.json b/package-lock.json index 7c1c57a..d9ef68f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "name": "next-url-shortener", "version": "0.1.0", "dependencies": { + "@hookform/resolvers": "^3.3.4", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", @@ -20,10 +22,12 @@ "next-themes": "^0.2.1", "react": "^18", "react-dom": "^18", + "react-hook-form": "^7.50.1", "surrealdb.js": "^0.11.0", "swr": "^2.2.4", "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.22.4" }, "devDependencies": { "@types/node": "^20", @@ -193,6 +197,14 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" }, + "node_modules/@hookform/resolvers": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz", + "integrity": "sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==", + "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", @@ -918,6 +930,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz", + "integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "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-menu": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", @@ -9408,6 +9443,21 @@ "loose-envify": "^1.1.0" } }, + "node_modules/react-hook-form": { + "version": "7.50.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.50.1.tgz", + "integrity": "sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", @@ -10902,14 +10952,6 @@ } } }, - "node_modules/surrealdb.js/node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/swr": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz", @@ -11330,6 +11372,14 @@ }, "node_modules/ws": { "peer": true + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index c5b5252..04455fa 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ "lint": "next lint" }, "dependencies": { + "@hookform/resolvers": "^3.3.4", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", @@ -21,10 +23,12 @@ "next-themes": "^0.2.1", "react": "^18", "react-dom": "^18", + "react-hook-form": "^7.50.1", "surrealdb.js": "^0.11.0", "swr": "^2.2.4", "tailwind-merge": "^2.2.1", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.22.4" }, "devDependencies": { "@types/node": "^20", @@ -40,4 +44,4 @@ "tailwindcss": "^3.4.1", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx new file mode 100644 index 0000000..294e437 --- /dev/null +++ b/src/app/create/page.tsx @@ -0,0 +1,71 @@ +"use client" +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardHeader, + CardTitle +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +const formSchema = z.object({ + url: z.string().min(4, + { message: "The URL must be at least 4 characters long" } + ).max(100 + , { message: "The URL must be at most 100 characters long" }), +}) + +export default function Home() { + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + url: "", + }, + }) + + function onSubmit(values: z.infer) { + console.log(values) + } + + return ( +
+ + + Create a Shortened URL + + +
+ + ( + + Enter url + + + + + + )} + /> + + + +
+
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 3afe659..cd4d4d0 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,7 +1,29 @@ +"use client" +import { + Card, + CardHeader, + CardTitle +} from "@/components/ui/card"; +import Link from "next/link"; + export default function Home() { return ( -
-

Home

-
+
+ + + + Create a Shortened URL + + + + + + + + Site Stats + + + +
); } diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx index 3302d89..b1a3413 100644 --- a/src/components/Nav.tsx +++ b/src/components/Nav.tsx @@ -8,15 +8,20 @@ export default function Nav() { Next Url Shortener -
- create +
+ Create
-
- create +
+ Stats
-
+ +
+
+ GitHub +
+
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx new file mode 100644 index 0000000..f6afdaf --- /dev/null +++ b/src/components/ui/form.tsx @@ -0,0 +1,176 @@ +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 ( +