style(auth): redesign login and onboarding pages

This commit is contained in:
Nicolas Meienberger
2025-10-25 17:05:20 +02:00
parent d58c4f793d
commit 47ff720adb
7 changed files with 145 additions and 121 deletions

View File

@@ -1,5 +1,6 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css"; @import "tw-animate-css";
@import "dither-plugin";
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));

View File

@@ -0,0 +1,34 @@
import { Mountain } from "lucide-react";
import type { ReactNode } from "react";
type AuthLayoutProps = {
title: string;
description: string;
children: ReactNode;
};
export function AuthLayout({ title, description, children }: AuthLayoutProps) {
return (
<div className="flex min-h-screen">
<div className="flex flex-1 items-center justify-center bg-background p-8">
<div className="w-full max-w-md space-y-8">
<div className="flex items-center gap-3">
<Mountain className="size-5 text-strong-accent" />
<span className="text-lg font-semibold">Ironmount</span>
</div>
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">{title}</h1>
<p className="text-sm text-muted-foreground">{description}</p>
</div>
{children}
</div>
</div>
<div
className="hidden lg:block lg:flex-1 dither-xl bg-cover bg-center"
style={{ backgroundImage: "url(/background.jpg)" }}
/>
</div>
);
}

View File

@@ -5,9 +5,8 @@ import { useForm } from "react-hook-form";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { loginMutation } from "~/api-client/@tanstack/react-query.gen"; import { loginMutation } from "~/api-client/@tanstack/react-query.gen";
import { GridBackground } from "~/components/grid-background"; import { AuthLayout } from "~/components/auth-layout";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
@@ -50,13 +49,7 @@ export default function LoginPage() {
}; };
return ( return (
<GridBackground className="flex items-center justify-center p-4"> <AuthLayout title="Login to your account" description="Enter your credentials below to login to your account">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
<CardDescription>Sign in to your account</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField <FormField
@@ -66,13 +59,7 @@ export default function LoginPage() {
<FormItem> <FormItem>
<FormLabel>Username</FormLabel> <FormLabel>Username</FormLabel>
<FormControl> <FormControl>
<Input <Input {...field} type="text" placeholder="admin" disabled={login.isPending} autoFocus />
{...field}
type="text"
placeholder="Enter your username"
disabled={login.isPending}
autoFocus
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@@ -83,21 +70,28 @@ export default function LoginPage() {
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<div className="flex items-center justify-between">
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<button
type="button"
className="text-xs text-muted-foreground hover:underline"
onClick={() => toast.info("Password reset not implemented")}
>
Forgot your password?
</button>
</div>
<FormControl> <FormControl>
<Input {...field} type="password" placeholder="Enter your password" disabled={login.isPending} /> <Input {...field} type="password" disabled={login.isPending} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<Button type="submit" className="w-full" loading={login.isPending}> <Button type="submit" className="w-full" loading={login.isPending}>
Sign In Login
</Button> </Button>
</form> </form>
</Form> </Form>
</CardContent> </AuthLayout>
</Card>
</GridBackground>
); );
} }

View File

@@ -5,9 +5,8 @@ import { useForm } from "react-hook-form";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
import { registerMutation } from "~/api-client/@tanstack/react-query.gen"; import { registerMutation } from "~/api-client/@tanstack/react-query.gen";
import { GridBackground } from "~/components/grid-background"; import { AuthLayout } from "~/components/auth-layout";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
@@ -61,13 +60,7 @@ export default function OnboardingPage() {
}; };
return ( return (
<GridBackground className="flex items-center justify-center p-4"> <AuthLayout title="Welcome to Ironmount" description="Create the admin user to get started">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl font-bold">Welcome to Ironmount</CardTitle>
<CardDescription>Create the admin user to get started</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField <FormField
@@ -126,8 +119,6 @@ export default function OnboardingPage() {
</Button> </Button>
</form> </form>
</Form> </Form>
</CardContent> </AuthLayout>
</Card>
</GridBackground>
); );
} }

View File

@@ -30,6 +30,7 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dither-plugin": "^1.1.1",
"isbot": "^5.1.31", "isbot": "^5.1.31",
"lucide-react": "^0.546.0", "lucide-react": "^0.546.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -32,6 +32,7 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dither-plugin": "^1.1.1",
"isbot": "^5.1.31", "isbot": "^5.1.31",
"lucide-react": "^0.546.0", "lucide-react": "^0.546.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
@@ -720,6 +721,8 @@
"detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
"dither-plugin": ["dither-plugin@1.1.1", "", {}, "sha512-PsgAcSoNVKkwh+Q/OopRn/2qb9HW1LRyGqT1bQe8iooYvVY1FIIqePFN9JkEIVK9rkfZdj7nXn9EUB4B7mNh6g=="],
"docker-modem": ["docker-modem@5.0.6", "", { "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", "split-ca": "^1.0.1", "ssh2": "^1.15.0" } }, "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ=="], "docker-modem": ["docker-modem@5.0.6", "", { "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", "split-ca": "^1.0.1", "ssh2": "^1.15.0" } }, "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ=="],
"dockerode": ["dockerode@4.0.9", "", { "dependencies": { "@balena/dockerignore": "^1.0.2", "@grpc/grpc-js": "^1.11.1", "@grpc/proto-loader": "^0.7.13", "docker-modem": "^5.0.6", "protobufjs": "^7.3.2", "tar-fs": "^2.1.4", "uuid": "^10.0.0" } }, "sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q=="], "dockerode": ["dockerode@4.0.9", "", { "dependencies": { "@balena/dockerignore": "^1.0.2", "@grpc/grpc-js": "^1.11.1", "@grpc/proto-loader": "^0.7.13", "docker-modem": "^5.0.6", "protobufjs": "^7.3.2", "tar-fs": "^2.1.4", "uuid": "^10.0.0" } }, "sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q=="],