feat: Add next login page with shadcn/ui #3221 (#3231)

### What problem does this PR solve?

feat: Add next login page with shadcn/ui  #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2024-11-06 11:13:04 +08:00
committed by GitHub
parent af74bf01c0
commit 601a128cd3
26 changed files with 3376 additions and 37 deletions

49
web/src/pages/demo.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { useTheme } from '@/components/theme-provider';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Moon, Sun } from 'lucide-react';
export function ModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
const Demo = () => {
return (
<div>
<div>
<ModeToggle></ModeToggle>
</div>
<Button>Destructive</Button>
</div>
);
};
export default Demo;

View File

@@ -0,0 +1,246 @@
'use client';
import { toast } from '@/components/hooks/use-toast';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
InputOTP,
InputOTPGroup,
InputOTPSlot,
} from '@/components/ui/input-otp';
import { useTranslate } from '@/hooks/common-hooks';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export function SignUpForm() {
const { t } = useTranslate('login');
const FormSchema = z.object({
email: z.string().email({
message: t('emailPlaceholder'),
}),
nickname: z.string({ required_error: t('nicknamePlaceholder') }),
password: z.string({ required_error: t('passwordPlaceholder') }),
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
email: '',
},
});
function onSubmit(data: z.infer<typeof FormSchema>) {
console.log('🚀 ~ onSubmit ~ data:', data);
toast({
title: 'You submitted the following values:',
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{t('emailLabel')}</FormLabel>
<FormControl>
<Input placeholder={t('emailPlaceholder')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="nickname"
render={({ field }) => (
<FormItem>
<FormLabel>{t('nicknameLabel')}</FormLabel>
<FormControl>
<Input placeholder={t('nicknamePlaceholder')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>{t('passwordLabel')}</FormLabel>
<FormControl>
<Input
type={'password'}
placeholder={t('passwordPlaceholder')}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
{t('signUp')}
</Button>
</form>
</Form>
);
}
export function SignInForm() {
const { t } = useTranslate('login');
const FormSchema = z.object({
email: z.string().email({
message: t('emailPlaceholder'),
}),
password: z.string({ required_error: t('passwordPlaceholder') }),
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
email: '',
},
});
function onSubmit(data: z.infer<typeof FormSchema>) {
console.log('🚀 ~ onSubmit ~ data:', data);
toast({
title: 'You submitted the following values:',
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{t('emailLabel')}</FormLabel>
<FormControl>
<Input placeholder={t('emailPlaceholder')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>{t('passwordLabel')}</FormLabel>
<FormControl>
<Input
type={'password'}
placeholder={t('passwordPlaceholder')}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center space-x-2">
<Checkbox id="terms" />
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{t('rememberMe')}
</label>
</div>
<Button type="submit" className="w-full">
{t('login')}
</Button>
</form>
</Form>
);
}
export function VerifyEmailForm() {
const FormSchema = z.object({
pin: z.string().min(6, {
message: 'Your one-time password must be 6 characters.',
}),
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
pin: '',
},
});
function onSubmit(data: z.infer<typeof FormSchema>) {
console.log('🚀 ~ onSubmit ~ data:', data);
toast({
title: 'You submitted the following values:',
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
});
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="pin"
render={({ field }) => (
<FormItem>
<FormLabel>One-Time Password</FormLabel>
<FormControl>
<InputOTP maxLength={6} {...field}>
<InputOTPGroup>
<InputOTPSlot index={0} />
<InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Verify
</Button>
</form>
</Form>
);
}

View File

@@ -0,0 +1,88 @@
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { useTranslate } from '@/hooks/common-hooks';
import { DiscordLogoIcon, GitHubLogoIcon } from '@radix-ui/react-icons';
import { SignInForm, SignUpForm, VerifyEmailForm } from './form';
function LoginFooter() {
return (
<section className="pt-[30px]">
<Separator />
<p className="text-center pt-[20px]">or continue with</p>
<div className="flex gap-4 justify-center pt-[20px]">
<GitHubLogoIcon className="w-8 h-8"></GitHubLogoIcon>
<DiscordLogoIcon className="w-8 h-8"></DiscordLogoIcon>
</div>
</section>
);
}
export function SignUpCard() {
const { t } = useTranslate('login');
return (
<Card className="w-[400px]">
<CardHeader>
<CardTitle>{t('signUp')}</CardTitle>
</CardHeader>
<CardContent>
<SignUpForm></SignUpForm>
<LoginFooter></LoginFooter>
</CardContent>
</Card>
);
}
export function SignInCard() {
const { t } = useTranslate('login');
return (
<Card className="w-[400px]">
<CardHeader>
<CardTitle>{t('login')}</CardTitle>
</CardHeader>
<CardContent>
<SignInForm></SignInForm>
</CardContent>
</Card>
);
}
export function VerifyEmailCard() {
// const { t } = useTranslate('login');
return (
<Card className="w-[400px]">
<CardHeader>
<CardTitle>Verify email</CardTitle>
</CardHeader>
<CardContent>
<section className="flex gap-y-6 flex-col">
<div className="flex items-center space-x-4">
<div className="flex-1 space-y-1">
<p className="text-sm font-medium leading-none">
Weve sent a 6-digit code to
</p>
<p className="text-sm text-blue-500">yifanwu92@gmail.com.</p>
</div>
<Button>Resend</Button>
</div>
<VerifyEmailForm></VerifyEmailForm>
</section>
</CardContent>
</Card>
);
}
const Login = () => {
return (
<>
<SignUpCard></SignUpCard>
<SignInCard></SignInCard>
<VerifyEmailCard></VerifyEmailCard>
</>
);
};
export default Login;