mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
style(auth): redesign login and onboarding pages
This commit is contained in:
@@ -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 *));
|
||||||
|
|
||||||
|
|||||||
34
apps/client/app/components/auth-layout.tsx
Normal file
34
apps/client/app/components/auth-layout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,54 +49,49 @@ 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">
|
<Form {...form}>
|
||||||
<CardHeader>
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
|
<FormField
|
||||||
<CardDescription>Sign in to your account</CardDescription>
|
control={form.control}
|
||||||
</CardHeader>
|
name="username"
|
||||||
<CardContent>
|
render={({ field }) => (
|
||||||
<Form {...form}>
|
<FormItem>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<FormLabel>Username</FormLabel>
|
||||||
<FormField
|
<FormControl>
|
||||||
control={form.control}
|
<Input {...field} type="text" placeholder="admin" disabled={login.isPending} autoFocus />
|
||||||
name="username"
|
</FormControl>
|
||||||
render={({ field }) => (
|
<FormMessage />
|
||||||
<FormItem>
|
</FormItem>
|
||||||
<FormLabel>Username</FormLabel>
|
)}
|
||||||
<FormControl>
|
/>
|
||||||
<Input
|
<FormField
|
||||||
{...field}
|
control={form.control}
|
||||||
type="text"
|
name="password"
|
||||||
placeholder="Enter your username"
|
render={({ field }) => (
|
||||||
disabled={login.isPending}
|
<FormItem>
|
||||||
autoFocus
|
<div className="flex items-center justify-between">
|
||||||
/>
|
<FormLabel>Password</FormLabel>
|
||||||
</FormControl>
|
<button
|
||||||
<FormMessage />
|
type="button"
|
||||||
</FormItem>
|
className="text-xs text-muted-foreground hover:underline"
|
||||||
)}
|
onClick={() => toast.info("Password reset not implemented")}
|
||||||
/>
|
>
|
||||||
<FormField
|
Forgot your password?
|
||||||
control={form.control}
|
</button>
|
||||||
name="password"
|
</div>
|
||||||
render={({ field }) => (
|
<FormControl>
|
||||||
<FormItem>
|
<Input {...field} type="password" disabled={login.isPending} />
|
||||||
<FormLabel>Password</FormLabel>
|
</FormControl>
|
||||||
<FormControl>
|
<FormMessage />
|
||||||
<Input {...field} type="password" placeholder="Enter your password" disabled={login.isPending} />
|
</FormItem>
|
||||||
</FormControl>
|
)}
|
||||||
<FormMessage />
|
/>
|
||||||
</FormItem>
|
<Button type="submit" className="w-full" loading={login.isPending}>
|
||||||
)}
|
Login
|
||||||
/>
|
</Button>
|
||||||
<Button type="submit" className="w-full" loading={login.isPending}>
|
</form>
|
||||||
Sign In
|
</Form>
|
||||||
</Button>
|
</AuthLayout>
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</GridBackground>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,73 +60,65 @@ 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">
|
<Form {...form}>
|
||||||
<CardHeader>
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
<CardTitle className="text-2xl font-bold">Welcome to Ironmount</CardTitle>
|
<FormField
|
||||||
<CardDescription>Create the admin user to get started</CardDescription>
|
control={form.control}
|
||||||
</CardHeader>
|
name="username"
|
||||||
<CardContent>
|
render={({ field }) => (
|
||||||
<Form {...form}>
|
<FormItem>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<FormLabel>Username</FormLabel>
|
||||||
<FormField
|
<FormControl>
|
||||||
control={form.control}
|
<Input {...field} type="text" placeholder="admin" disabled={registerUser.isPending} autoFocus />
|
||||||
name="username"
|
</FormControl>
|
||||||
render={({ field }) => (
|
<FormDescription>Choose a username for the admin account</FormDescription>
|
||||||
<FormItem>
|
<FormMessage />
|
||||||
<FormLabel>Username</FormLabel>
|
</FormItem>
|
||||||
<FormControl>
|
)}
|
||||||
<Input {...field} type="text" placeholder="admin" disabled={registerUser.isPending} autoFocus />
|
/>
|
||||||
</FormControl>
|
<FormField
|
||||||
<FormDescription>Choose a username for the admin account</FormDescription>
|
control={form.control}
|
||||||
<FormMessage />
|
name="password"
|
||||||
</FormItem>
|
render={({ field }) => (
|
||||||
)}
|
<FormItem>
|
||||||
/>
|
<FormLabel>Password</FormLabel>
|
||||||
<FormField
|
<FormControl>
|
||||||
control={form.control}
|
<Input
|
||||||
name="password"
|
{...field}
|
||||||
render={({ field }) => (
|
type="password"
|
||||||
<FormItem>
|
placeholder="Enter a secure password"
|
||||||
<FormLabel>Password</FormLabel>
|
disabled={registerUser.isPending}
|
||||||
<FormControl>
|
/>
|
||||||
<Input
|
</FormControl>
|
||||||
{...field}
|
<FormDescription>Password must be at least 8 characters long.</FormDescription>
|
||||||
type="password"
|
<FormMessage />
|
||||||
placeholder="Enter a secure password"
|
</FormItem>
|
||||||
disabled={registerUser.isPending}
|
)}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
<FormField
|
||||||
<FormDescription>Password must be at least 8 characters long.</FormDescription>
|
control={form.control}
|
||||||
<FormMessage />
|
name="confirmPassword"
|
||||||
</FormItem>
|
render={({ field }) => (
|
||||||
)}
|
<FormItem>
|
||||||
/>
|
<FormLabel>Confirm Password</FormLabel>
|
||||||
<FormField
|
<FormControl>
|
||||||
control={form.control}
|
<Input
|
||||||
name="confirmPassword"
|
{...field}
|
||||||
render={({ field }) => (
|
type="password"
|
||||||
<FormItem>
|
placeholder="Re-enter your password"
|
||||||
<FormLabel>Confirm Password</FormLabel>
|
disabled={registerUser.isPending}
|
||||||
<FormControl>
|
/>
|
||||||
<Input
|
</FormControl>
|
||||||
{...field}
|
<FormMessage />
|
||||||
type="password"
|
</FormItem>
|
||||||
placeholder="Re-enter your password"
|
)}
|
||||||
disabled={registerUser.isPending}
|
/>
|
||||||
/>
|
<Button type="submit" className="w-full" loading={registerUser.isPending}>
|
||||||
</FormControl>
|
Create Admin User
|
||||||
<FormMessage />
|
</Button>
|
||||||
</FormItem>
|
</form>
|
||||||
)}
|
</Form>
|
||||||
/>
|
</AuthLayout>
|
||||||
<Button type="submit" className="w-full" loading={registerUser.isPending}>
|
|
||||||
Create Admin User
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</GridBackground>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
BIN
apps/client/public/background.jpg
Normal file
BIN
apps/client/public/background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
3
bun.lock
3
bun.lock
@@ -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=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user