From c1a35f3cf86cd04ca10378725a4a01d1e307a85b Mon Sep 17 00:00:00 2001 From: Lorenzo Demaio Date: Tue, 11 Nov 2025 15:14:10 +0100 Subject: [PATCH 1/9] feat(auth): implement login and signup pages with forms --- .../(auth)/login/components/login-form.tsx | 64 +++++ apps/frontend/app/(auth)/login/page.tsx | 32 +++ .../(auth)/signup/components/signup-form.tsx | 73 ++++++ apps/frontend/app/(auth)/signup/page.tsx | 31 +++ apps/frontend/app/login/page.tsx | 13 - packages/ui/package.json | 2 + packages/ui/src/components/field.tsx | 248 ++++++++++++++++++ packages/ui/src/components/input.tsx | 21 ++ packages/ui/src/components/label.tsx | 24 ++ packages/ui/src/components/separator.tsx | 28 ++ pnpm-lock.yaml | 72 +++++ 11 files changed, 595 insertions(+), 13 deletions(-) create mode 100644 apps/frontend/app/(auth)/login/components/login-form.tsx create mode 100644 apps/frontend/app/(auth)/login/page.tsx create mode 100644 apps/frontend/app/(auth)/signup/components/signup-form.tsx create mode 100644 apps/frontend/app/(auth)/signup/page.tsx delete mode 100644 apps/frontend/app/login/page.tsx create mode 100644 packages/ui/src/components/field.tsx create mode 100644 packages/ui/src/components/input.tsx create mode 100644 packages/ui/src/components/label.tsx create mode 100644 packages/ui/src/components/separator.tsx diff --git a/apps/frontend/app/(auth)/login/components/login-form.tsx b/apps/frontend/app/(auth)/login/components/login-form.tsx new file mode 100644 index 0000000..fc56e7d --- /dev/null +++ b/apps/frontend/app/(auth)/login/components/login-form.tsx @@ -0,0 +1,64 @@ +import { cn } from "@workspace/ui/lib/utils" +import { Button } from "@workspace/ui/components/button" +import { + Field, + FieldDescription, + FieldGroup, + FieldLabel, + FieldSeparator, + } from "@workspace/ui/components/field" +import { Input } from "@workspace/ui/components/input" +import Link from "next/link" +export function LoginForm({ + className, + ...props +}: React.ComponentProps<"form">) { + return ( +
+ +
+

Login to your account

+

+ Enter your email below to login to your account +

+
+ + Email + + + +
+ Password + + Forgot your password? + +
+ +
+ + + + Or continue with + + + + Don't have an account?{" "} + Sign up + + + +
+
+ ) +} \ No newline at end of file diff --git a/apps/frontend/app/(auth)/login/page.tsx b/apps/frontend/app/(auth)/login/page.tsx new file mode 100644 index 0000000..bcb1218 --- /dev/null +++ b/apps/frontend/app/(auth)/login/page.tsx @@ -0,0 +1,32 @@ +import { GalleryVerticalEnd } from "lucide-react" +import { LoginForm } from "./components/login-form" + + +export default function SignupPage() { + return ( +
+
+
+ +
+ +
+ Acme Inc. +
+
+
+
+ +
+
+
+
+ Image +
+
+ ) +} \ No newline at end of file diff --git a/apps/frontend/app/(auth)/signup/components/signup-form.tsx b/apps/frontend/app/(auth)/signup/components/signup-form.tsx new file mode 100644 index 0000000..e0449f7 --- /dev/null +++ b/apps/frontend/app/(auth)/signup/components/signup-form.tsx @@ -0,0 +1,73 @@ +import { cn } from "@workspace/ui/lib/utils" + +import { Button } from "@workspace/ui/components/button" +import { + Field, + FieldDescription, + FieldGroup, + FieldLabel, + FieldSeparator, +} from "@workspace/ui/components/field" + +import { Input } from "@workspace/ui/components/input" +import Link from "next/link" + +export function SignupForm({ + className, + ...props +}: React.ComponentProps<"form">) { + return ( +
+ +
+

Create your account

+

+ Fill in the form below to create your account +

+
+ + Full Name + + + + Email + + + We'll use this to contact you. We will not share your email + with anyone else. + + + + Password + + + Must be at least 8 characters long. + + + + Confirm Password + + Please confirm your password. + + + + + Or continue with + + + + Already have an account? Sign in + + +
+
+ ) +} diff --git a/apps/frontend/app/(auth)/signup/page.tsx b/apps/frontend/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..67e94be --- /dev/null +++ b/apps/frontend/app/(auth)/signup/page.tsx @@ -0,0 +1,31 @@ +import { GalleryVerticalEnd } from "lucide-react" +import { SignupForm } from "../signup/components/signup-form" + +export default function SignupPage() { + return ( +
+
+
+ +
+ +
+ Senv.dev +
+
+
+
+ +
+
+
+
+ Image +
+
+ ) +} \ No newline at end of file diff --git a/apps/frontend/app/login/page.tsx b/apps/frontend/app/login/page.tsx deleted file mode 100644 index 19f8dd7..0000000 --- a/apps/frontend/app/login/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Button } from "@workspace/ui/components/button" - -import Link from "next/link"; - -export default function LoginPage() { - return ( -
-

Login

- Link to open - -
- ) -} \ No newline at end of file diff --git a/packages/ui/package.json b/packages/ui/package.json index 944ae93..73253e8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -7,6 +7,8 @@ "lint": "eslint . --max-warnings 0" }, "dependencies": { + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/packages/ui/src/components/field.tsx b/packages/ui/src/components/field.tsx new file mode 100644 index 0000000..36cb298 --- /dev/null +++ b/packages/ui/src/components/field.tsx @@ -0,0 +1,248 @@ +"use client" + +import { useMemo } from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@workspace/ui/lib/utils" +import { Label } from "@workspace/ui/components/label" +import { Separator } from "@workspace/ui/components/separator" + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className + )} + {...props} + /> + ) +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ) +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-slot=field-group]]:gap-4", + className + )} + {...props} + /> + ) +} + +const fieldVariants = cva( + "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", + { + variants: { + orientation: { + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], + horizontal: [ + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + responsive: [ + "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + } +) + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +