From 701ce13ef5813daaba9590226dada68ab2cee2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Gonz=C3=A1lez=20Mu=C3=B1oz?= Date: Mon, 7 Oct 2024 18:10:57 +0200 Subject: [PATCH] implement signing process and page --- client/.env.development | 2 +- client/components.json | 20 ++ client/package.json | 16 +- .../src/app/auth/api/[...nextauth]/config.ts | 1 + client/src/app/auth/signin/page.tsx | 15 ++ client/src/app/globals.css | 79 +++++-- client/src/app/page.tsx | 112 ++------- client/src/app/profile/page.tsx | 14 ++ client/src/components/ui/button.tsx | 58 +++++ client/src/components/ui/form.tsx | 182 +++++++++++++++ client/src/components/ui/input.tsx | 25 ++ client/src/components/ui/label.tsx | 27 +++ .../src/containers/auth/signin/form/index.tsx | 116 +++++++++ client/src/containers/auth/signin/index.tsx | 27 +++ client/src/containers/profile/index.tsx | 26 +++ client/src/lib/queryClient.ts | 2 +- client/src/lib/utils.ts | 6 + client/{ => src}/middleware.ts | 14 -- client/tailwind.config.ts | 53 ++++- pnpm-lock.yaml | 221 +++++++++++++++++- 20 files changed, 877 insertions(+), 139 deletions(-) create mode 100644 client/components.json create mode 100644 client/src/app/auth/signin/page.tsx create mode 100644 client/src/app/profile/page.tsx create mode 100644 client/src/components/ui/button.tsx create mode 100644 client/src/components/ui/form.tsx create mode 100644 client/src/components/ui/input.tsx create mode 100644 client/src/components/ui/label.tsx create mode 100644 client/src/containers/auth/signin/form/index.tsx create mode 100644 client/src/containers/auth/signin/index.tsx create mode 100644 client/src/containers/profile/index.tsx create mode 100644 client/src/lib/utils.ts rename client/{ => src}/middleware.ts (51%) diff --git a/client/.env.development b/client/.env.development index d99a62f9..5acc95e2 100644 --- a/client/.env.development +++ b/client/.env.development @@ -1,3 +1,3 @@ NEXTAUTH_URL=http://localhost:$PORT NEXTAUTH_SECRET=WAzjpS46vFxp17TsRDU3FXo+TF0vrfy6uhCXwGMBUE8= -NEXT_PUBLIC_API_URL=http://localhost:4000 \ No newline at end of file +NEXT_PUBLIC_API_URL=https://dev.blue-carbon-cost-tool.dev-vizzuality.com/api \ No newline at end of file diff --git a/client/components.json b/client/components.json new file mode 100644 index 00000000..42f059b5 --- /dev/null +++ b/client/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} \ No newline at end of file diff --git a/client/package.json b/client/package.json index 99b18a77..9182da03 100644 --- a/client/package.json +++ b/client/package.json @@ -9,12 +9,22 @@ "lint": "next lint" }, "dependencies": { + "@hookform/resolvers": "3.9.0", + "@radix-ui/react-icons": "1.3.0", + "@radix-ui/react-label": "2.1.0", + "@radix-ui/react-separator": "1.1.0", + "@radix-ui/react-slot": "1.1.0", "@tanstack/react-query": "5.59.0", "@ts-rest/react-query": "3.51.0", + "class-variance-authority": "0.7.0", + "clsx": "2.1.1", + "lucide-react": "0.447.0", "next": "14.2.10", "next-auth": "4.24.8", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "tailwind-merge": "2.5.3", + "tailwindcss-animate": "1.0.7" }, "devDependencies": { "@types/node": "catalog:", @@ -26,7 +36,9 @@ "postcss": "^8", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.8", + "react-hook-form": "7.53.0", "tailwindcss": "^3.4.1", - "typescript": "catalog:" + "typescript": "catalog:", + "zod": "catalog:" } } diff --git a/client/src/app/auth/api/[...nextauth]/config.ts b/client/src/app/auth/api/[...nextauth]/config.ts index 6e23ccef..eb319c15 100644 --- a/client/src/app/auth/api/[...nextauth]/config.ts +++ b/client/src/app/auth/api/[...nextauth]/config.ts @@ -83,6 +83,7 @@ export const config = { }, pages: { signIn: "/auth/signin", + signOut: "/", }, } as NextAuthOptions; diff --git a/client/src/app/auth/signin/page.tsx b/client/src/app/auth/signin/page.tsx new file mode 100644 index 00000000..76c7db1e --- /dev/null +++ b/client/src/app/auth/signin/page.tsx @@ -0,0 +1,15 @@ +import { redirect } from "next/navigation"; + +import { auth } from "@/app/auth/api/[...nextauth]/config"; + +import SignIn from "@/containers/auth/signin"; + +export default async function SignInPage() { + const session = await auth(); + + if (session) { + redirect("/profile"); + } + + return ; +} diff --git a/client/src/app/globals.css b/client/src/app/globals.css index 13d40b89..1dcb0fc6 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -2,21 +2,7 @@ @tailwind components; @tailwind utilities; -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - body { - color: var(--foreground); - background: var(--background); font-family: Arial, Helvetica, sans-serif; } @@ -25,3 +11,68 @@ body { text-wrap: balance; } } + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 688cafd1..bc30c89d 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -1,101 +1,23 @@ -import Image from "next/image"; +import Link from "next/link"; -export default function Home() { - return ( -
-
- Next.js logo +import { auth } from "@/app/auth/api/[...nextauth]/config"; + +import { Button } from "@/components/ui/button"; -
    -
  1. - Placeholder for{" "} - - Blue Carbon Cost Tool - - . -
  2. -
+export default async function Home() { + const session = await auth(); + + return ( +
+

Welcome to Blue Carbon Cost

- -
- +
); } diff --git a/client/src/app/profile/page.tsx b/client/src/app/profile/page.tsx new file mode 100644 index 00000000..58d524c3 --- /dev/null +++ b/client/src/app/profile/page.tsx @@ -0,0 +1,14 @@ +import { QueryClient, dehydrate } from "@tanstack/react-query"; +import { HydrationBoundary } from "@tanstack/react-query"; + +import Profile from "@/containers/profile"; + +export default async function ProfilePage() { + const queryClient = new QueryClient(); + + return ( + + + + ); +} diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx new file mode 100644 index 00000000..0d7d377d --- /dev/null +++ b/client/src/components/ui/button.tsx @@ -0,0 +1,58 @@ +import * as React from "react"; + +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/client/src/components/ui/form.tsx b/client/src/components/ui/form.tsx new file mode 100644 index 00000000..d5fcbe2e --- /dev/null +++ b/client/src/components/ui/form.tsx @@ -0,0 +1,182 @@ +"use client"; + +import * as React from "react"; + +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; + +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 ( +