refactor: restyle login page with UI components
Improves the login page's visual appearance by using Card and Input components.
This commit is contained in:
parent
a56b6829a2
commit
8ab50b2c5e
6 changed files with 142 additions and 45 deletions
|
@ -4,6 +4,7 @@ import LoginForm from '@/components/user/login-form';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import '@/app/globals.css';
|
import '@/app/globals.css';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
|
||||||
export default async function LoginPage() {
|
export default async function LoginPage() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
|
@ -14,17 +15,20 @@ export default async function LoginPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col items-center justify-center h-screen'>
|
<div className='flex flex-col items-center justify-center h-screen'>
|
||||||
<div className='flex flex-col items-center justify-center w-full max-w-sm p-15 gap-10 bg-white border border-gray-300 rounded-lg shadow-md'>
|
<Card className='w-[350px] max-w-screen'>
|
||||||
<h1>Login</h1>
|
<CardHeader>
|
||||||
|
<CardTitle className='text-lg text-center'>Login</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='gap-6 flex flex-col'>
|
||||||
|
<LoginForm />
|
||||||
|
|
||||||
<LoginForm></LoginForm>
|
<hr />
|
||||||
|
|
||||||
<hr style={{ width: 230 }} />
|
|
||||||
|
|
||||||
{process.env.AUTH_AUTHENTIK_ISSUER && (
|
{process.env.AUTH_AUTHENTIK_ISSUER && (
|
||||||
<SSOLogin provider='authentik' providerDisplayName='SSO' />
|
<SSOLogin provider='authentik' providerDisplayName='SSO' />
|
||||||
)}
|
)}
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,28 @@
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
|
||||||
export default function LabeledInput({
|
export default function LabeledInput({
|
||||||
type,
|
type,
|
||||||
width,
|
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
}: {
|
}: {
|
||||||
type: 'text' | 'email' | 'password';
|
type: 'text' | 'email' | 'password';
|
||||||
width?: number;
|
label: string;
|
||||||
label?: string;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
}) {
|
}) {
|
||||||
const randomId = Math.random().toString(36).substring(2, 15);
|
const randomId = Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
if (!label) {
|
|
||||||
return (
|
|
||||||
<div className='flex flex-col gap-2'>
|
|
||||||
<input
|
|
||||||
className='border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-blue-500'
|
|
||||||
type={type}
|
|
||||||
placeholder={placeholder}
|
|
||||||
defaultValue={value}
|
|
||||||
style={{
|
|
||||||
width: width ? `${width}px` : '100%',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
<label htmlFor={randomId}>{label}</label>
|
<label htmlFor={randomId}>{label}</label>
|
||||||
|
|
||||||
<input
|
<Input
|
||||||
className='border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-blue-500'
|
|
||||||
id={randomId}
|
|
||||||
type={type}
|
type={type}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
style={{
|
id={randomId}
|
||||||
width: width ? `${width}px` : '100%',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
92
src/components/ui/card.tsx
Normal file
92
src/components/ui/card.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Card({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card'
|
||||||
|
className={cn(
|
||||||
|
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card-header'
|
||||||
|
className={cn(
|
||||||
|
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card-title'
|
||||||
|
className={cn('leading-none font-semibold', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card-description'
|
||||||
|
className={cn('text-muted-foreground text-sm', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card-action'
|
||||||
|
className={cn(
|
||||||
|
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card-content'
|
||||||
|
className={cn('px-6', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot='card-footer'
|
||||||
|
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardFooter,
|
||||||
|
CardTitle,
|
||||||
|
CardAction,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
};
|
21
src/components/ui/input.tsx
Normal file
21
src/components/ui/input.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
data-slot='input'
|
||||||
|
className={cn(
|
||||||
|
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||||
|
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||||
|
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Input };
|
|
@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
export default function LoginForm() {
|
export default function LoginForm() {
|
||||||
return (
|
return (
|
||||||
<form className='flex flex-col gap-4 w-7/8'>
|
<form className='flex flex-col gap-4 w-full'>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='email'
|
type='email'
|
||||||
label='E-Mail'
|
label='E-Mail'
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default function SSOLogin({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className='flex flex-col items-center gap-4 w-7/8'
|
className='flex flex-col items-center gap-4 w-full'
|
||||||
action={async () => {
|
action={async () => {
|
||||||
'use server';
|
'use server';
|
||||||
await signIn(provider);
|
await signIn(provider);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue