-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: login, register pages #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| export default function AuthLayout({ children }: { children: React.ReactNode }) { | ||
| return ( | ||
| <main className="min-h-screen flex items-center justify-center bg-gray-100"> | ||
| <div className="w-full max-w-md p-8 bg-white rounded-xl shadow">{children}</div> | ||
| <main> | ||
| <div>{children}</div> | ||
| </main> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,10 @@ | |
| --color-sidebar-ring: var(--sidebar-ring); | ||
| } | ||
|
|
||
| @theme { | ||
| --color-link: oklch(54.6% 0.245 262.881); | ||
| } | ||
|
|
||
| :root { | ||
| --radius: 0.625rem; | ||
| --background: oklch(1 0 0); | ||
|
|
@@ -76,6 +80,7 @@ | |
| --sidebar-accent-foreground: oklch(0.205 0 0); | ||
| --sidebar-border: oklch(0.922 0 0); | ||
| --sidebar-ring: oklch(0.708 0 0); | ||
| --link: oklch(0.62 0.19 259); | ||
| } | ||
|
|
||
| .dark { | ||
|
|
@@ -119,4 +124,8 @@ | |
| body { | ||
| @apply bg-background text-foreground; | ||
| } | ||
| button:not(:disabled), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. это в shared/ui/button нужно |
||
| [role='button']:not(:disabled) { | ||
| cursor: pointer; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,55 +1,61 @@ | ||
| 'use client'; | ||
| import Link from 'next/link'; | ||
| import { useForm } from 'react-hook-form'; | ||
| import { zodResolver } from '@hookform/resolvers/zod'; | ||
| import { formSchema, FormState } from '../model/loginSchema'; | ||
| import { Field, FieldDescription, FieldLabel, Button, Input } from 'shared/ui'; | ||
| import { | ||
| Field, | ||
| FieldLabel, | ||
| Button, | ||
| Input, | ||
| FieldError, | ||
| Checkbox, | ||
| Label, | ||
| PasswordInput, | ||
| } from 'shared/ui'; | ||
|
|
||
| export function LoginForm() { | ||
|
kapitulin24 marked this conversation as resolved.
|
||
| const { | ||
| register, | ||
| handleSubmit, | ||
| reset, | ||
| formState: { errors }, | ||
| } = useForm({ | ||
| } = useForm<FormState>({ | ||
| resolver: zodResolver(formSchema), | ||
| mode: 'onChange', | ||
| }); | ||
| const onSubmit = (data: FormState): void => { | ||
| console.log(data); | ||
| reset(); | ||
| }; | ||
| return ( | ||
| <form className="space-y-6 mb-4" onSubmit={handleSubmit(onSubmit)}> | ||
| <div className="flex flex-col space-y-4"> | ||
| <form className="space-y-6 mb-6" onSubmit={handleSubmit(onSubmit)}> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mb-6 не нужен, этот отступ лучше задать другим способом в пэйдже |
||
| <div className="flex flex-col space-y-5"> | ||
| <Field> | ||
| <FieldLabel htmlFor="email">Почта</FieldLabel> | ||
| <Input type="email" {...register('email')} id="email" placeholder="example@company.com" /> | ||
| {errors.email && ( | ||
| <FieldDescription className="text-red-500">{errors.email.message}</FieldDescription> | ||
| )} | ||
| {errors.email && <FieldError className="text-red-500">{errors.email.message}</FieldError>} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. зачем тут кастомный text-red? |
||
| </Field> | ||
| <Field className="grid gap-2"> | ||
| <div className="flex items-center"> | ||
| <FieldLabel htmlFor="password">Пароль</FieldLabel> | ||
| <Link | ||
| href="/auth/reset-password" | ||
| className="ml-auto text-sm underline-offset-4 hover:underline text-muted-foreground" | ||
| > | ||
| Забыли пароль? | ||
| </Link> | ||
| </div> | ||
|
|
||
| <Input id="password" type="password" placeholder="********" {...register('password')} /> | ||
|
|
||
| <PasswordInput id="password" placeholder="Ваш пароль" {...register('password')} /> | ||
| {errors.password && ( | ||
| <FieldDescription className="text-red-500 text-sm"> | ||
| {errors.password.message} | ||
| </FieldDescription> | ||
| <FieldError className="text-red-500 text-sm">{errors.password.message}</FieldError> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. зачем тут кастомный text-red? еще и размер текста |
||
| )} | ||
| </Field> | ||
| <div className="flex items-center justify-between text-sm"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. label все равно переопределяет размер текста. если он нужен, то тогда нужно его только для link задать |
||
| <Label className="flex items-center gap-2"> | ||
| <Checkbox /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. нужен в схеме и контролируемый ввод |
||
| Запомнить меня | ||
| </Label> | ||
| <Link href="/auth/reset-password" className="text-link hover:underline"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. про линк где-то в регистрации писал уже |
||
| Забыли пароль? | ||
| </Link> | ||
| </div> | ||
| </div> | ||
| <Button className="w-full bg-blue-600 text-white py-2 rounded">Войти</Button> | ||
| <Button size="lg" className="w-full"> | ||
| Войти | ||
| </Button> | ||
| </form> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,34 @@ | ||
| 'use client'; | ||
|
kapitulin24 marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. должен работать как серверный, вроде ничего не мешает |
||
|
|
||
| import { LoginForm } from './LoginForm'; | ||
| import Link from 'next/link'; | ||
| import { ScreenshotPlaceholder, AppLogo } from 'shared/ui'; | ||
|
|
||
| export default function LoginPage() { | ||
| return ( | ||
| <div> | ||
| <h1 className="text-2xl font-semibold mb-2"># Task-tracker</h1> | ||
|
|
||
| <h2 className="text-lg font-medium mb-4">С возвращением</h2> | ||
| <LoginForm /> | ||
| <div className="flex flex-col justify-center items-center gap-10"> | ||
| <div className="flex gap-2"> | ||
| <p className="text-gray-500">Нет аккаунта?</p> | ||
| <Link className="text-blue-700" href="/auth/register"> | ||
| Зарегистрироваться | ||
| </Link> | ||
| <div className="min-h-screen flex"> | ||
| <div className="hidden md:flex flex-col justify-between bg-secondary p-10 w-1/2"> | ||
| <div> | ||
| <AppLogo /> | ||
| <h2 className="text-2xl mb-2 font-bold">С возвращением!</h2> | ||
| <p> | ||
| Войдите в свой аккаунт и продолжайте работу над проектами. <br /> | ||
| Ваша команда уже ждет вас. | ||
| </p> | ||
| </div> | ||
| <ScreenshotPlaceholder /> | ||
| </div> | ||
| <div className="flex flex-col justify-center items-center w-full md:w-1/2 p-10"> | ||
| <div className="w-full max-w-md"> | ||
| <h2 className="text-3xl font-bold mb-2">Вход в систему</h2> | ||
| <p className="text-gray-500 mb-6">Пожалуйста, введите ваши данные для входа.</p> | ||
| <LoginForm /> | ||
| <div className="mt-6 flex justify-center gap-2 text-sm text-gray-500"> | ||
| <span>Нет аккаунта?</span> | ||
| <Link className="text-link" href="/auth/register"> | ||
| Зарегистрироваться | ||
| </Link> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,19 @@ | ||
| import { z } from 'zod'; | ||
|
|
||
| export const formSchema = z.object({ | ||
| email: z.email('Неверный формат email'), | ||
| name: z.string().min(2, 'Слишком короткое имя').max(15, 'Слишком длинное имя'), | ||
| password: z.string().min(6, 'Минимум 6 символов').max(32, 'Слишком длинный пароль'), | ||
| }); | ||
| export const formSchema = z | ||
| .object({ | ||
| email: z.email('Неверный формат email'), | ||
| name: z.string().min(2, 'Слишком короткое имя').max(15, 'Слишком длинное имя'), | ||
| password: z.string().min(6, 'Минимум 6 символов').max(32, 'Слишком длинный пароль'), | ||
| confirmPassword: z.string(), | ||
| terms: z.boolean().refine((val) => val === true, { | ||
| message: 'Необходимо принять условия использования', | ||
| }), | ||
| }) | ||
|
|
||
| .refine((data) => data.password === data.confirmPassword, { | ||
| message: 'Пароли не совпадают', | ||
| path: ['confirmPassword'], | ||
| }); | ||
|
|
||
| export type FormState = z.infer<typeof formSchema>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,54 +1,80 @@ | ||
| 'use client'; | ||
|
|
||
| import { useForm } from 'react-hook-form'; | ||
| import { Controller, useForm } from 'react-hook-form'; | ||
| import { zodResolver } from '@hookform/resolvers/zod'; | ||
| import { Input } from 'shared/ui/input'; | ||
| import { formSchema, FormState } from '../model/registerSchema'; | ||
| import { Field, FieldDescription, FieldLabel } from 'shared/ui/field'; | ||
| import { Button } from 'shared/ui'; | ||
| import { Field, FieldLabel, Button, Checkbox, Label, FieldError, Input } from 'shared/ui'; | ||
| import Link from 'next/link'; | ||
| import { PasswordInput } from 'shared/ui/password-input'; | ||
|
|
||
| export function RegisterForm() { | ||
| const { | ||
| register, | ||
| handleSubmit, | ||
| reset, | ||
| control, | ||
| formState: { errors }, | ||
| } = useForm({ | ||
| } = useForm<FormState>({ | ||
| resolver: zodResolver(formSchema), | ||
| mode: 'onChange', | ||
| defaultValues: { | ||
| terms: false, | ||
| }, | ||
| }); | ||
| const onSubmit = (data: FormState): void => { | ||
| console.log(data); | ||
| reset(); | ||
| }; | ||
| return ( | ||
| <form className="space-y-6 mb-4" onSubmit={handleSubmit(onSubmit)}> | ||
| <div className="flex flex-col space-y-4"> | ||
| <Field> | ||
| <FieldLabel htmlFor="email">Почта</FieldLabel> | ||
| <Input type="email" {...register('email')} id="email" placeholder="example@company.com" /> | ||
| {errors.email && ( | ||
| <FieldDescription className="text-red-500">{errors.email.message}</FieldDescription> | ||
| )} | ||
| </Field> | ||
| <form className="space-y-6 mb-8" onSubmit={handleSubmit(onSubmit)}> | ||
| <div className="flex flex-col space-y-5"> | ||
| <Field> | ||
| <FieldLabel htmlFor="name">Имя</FieldLabel> | ||
| <Input id="name" type="text" placeholder="example" {...register('name')} /> | ||
| <Input id="name" type="text" placeholder="Алексей Смирнов" {...register('name')} /> | ||
| {errors.name && ( | ||
| <FieldDescription className="text-red-500 text-sm"> | ||
| {errors.name.message} | ||
| </FieldDescription> | ||
| <FieldError className="text-red-500 text-sm">{errors.name.message}</FieldError> | ||
| )} | ||
| </Field> | ||
| <Field> | ||
| <FieldLabel htmlFor="email">Почта</FieldLabel> | ||
| <Input type="email" {...register('email')} id="email" placeholder="example@company.com" /> | ||
| {errors.email && <FieldError className="text-red-500">{errors.email.message}</FieldError>} | ||
| </Field> | ||
| <Field> | ||
| <FieldLabel htmlFor="password">Пароль</FieldLabel> | ||
| <Input id="password" type="password" placeholder="*********" {...register('password')} /> | ||
| <PasswordInput id="password" placeholder="Ваш пароль" {...register('password')} /> | ||
| {errors.password && ( | ||
| <FieldDescription className="text-red-500">{errors.password.message}</FieldDescription> | ||
| <FieldError className="text-red-500">{errors.password.message}</FieldError> | ||
| )} | ||
| </Field> | ||
| <Field> | ||
| <FieldLabel htmlFor="confirmPassword">Подтвердите пароль</FieldLabel> | ||
| <PasswordInput | ||
| id="confirmPassword" | ||
| placeholder="*********" | ||
| {...register('confirmPassword')} | ||
| /> | ||
| {errors.confirmPassword && ( | ||
| <FieldError className="text-red-500">{errors.confirmPassword.message}</FieldError> | ||
| )} | ||
| </Field> | ||
| </div> | ||
| <Button className="w-full bg-blue-600 text-white py-2 rounded">Зарегистрироваться</Button> | ||
| <Label className="flex items-center gap-2 text-sm"> | ||
| <Controller | ||
| name="terms" | ||
| control={control} | ||
| render={({ field }) => ( | ||
| <Checkbox id="terms" checked={field.value} onCheckedChange={field.onChange} /> | ||
| )} | ||
| /> | ||
| Я согласен с | ||
| <Link href="/auth/reset-password" className="text-link hover:underline"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. нужно создать Link в shared/ui с нужными стилями |
||
| условиями использования | ||
| </Link> | ||
| </Label> | ||
| {errors.terms && ( | ||
| <FieldError className="text-red-500 text-xs ml-6">{errors.terms.message}</FieldError> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. уже писал про стили |
||
| )} | ||
| <Button size="lg" className="w-full"> | ||
| Зарегистрироваться | ||
| </Button> | ||
| </form> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,43 @@ | ||
| 'use client'; | ||
| import Link from 'next/link'; | ||
| import { RegisterForm } from './RegisterForm'; | ||
| import { ScreenshotPlaceholder, AppLogo } from 'shared/ui'; | ||
|
|
||
| export default function RegisterPage() { | ||
| return ( | ||
| <div> | ||
| <h1 className="text-2xl font-semibold mb-2"># Task-tracker</h1> | ||
| <h2 className="text-lg font-medium mb-4">С возвращением</h2> | ||
| <RegisterForm /> | ||
| <div className="min-h-screen flex"> | ||
| <div className="hidden md:flex flex-col justify-between bg-secondary p-10 w-1/2"> | ||
| <div> | ||
| <AppLogo /> | ||
| <div className="mb-4"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. лучше вместо mb-4 задать mt-4 ScreenshotPlaceholder |
||
| <h2 className="text-2xl font-bold mb-2">Начните работать эффективнее уже сегодня</h2> | ||
| <p> | ||
| Платформа, созданная для тех, кто ценит свое время. Управляйте проектами эффективнее | ||
| и прозрачнее. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <ScreenshotPlaceholder /> | ||
| </div> | ||
| <div className="flex flex-col justify-center items-center w-full md:w-1/2 p-10"> | ||
| <div className="w-full max-w-md"> | ||
| <div className="mb-8"> | ||
| <h2 className="text-3xl font-bold mb-2">Создать аккаунт</h2> | ||
| <p className="text-muted-foreground mb-6"> | ||
| Заполните форму ниже, чтобы начать работу. | ||
| </p> | ||
| </div> | ||
| <RegisterForm /> | ||
| <div className="flex gap-3 justify-center text-sm"> | ||
| <p className="text-muted-foreground mb-6">уже есть аккаунт?</p> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. вместо mb-6 для блока div pb-6 |
||
| <Link href="/auth/login" className="text-link hover:underline"> | ||
| Войти | ||
| </Link> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default as LogoImage } from './images/logo.svg'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import Image from 'next/image'; | ||
| import { LogoImage } from 'shared/assets'; | ||
|
|
||
| export function AppLogo() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. типизация. пропсы и возвращаемое значение
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. нужно добавить возможность выбора варианта логотипа и размера. Точно так же как это сделано в shared/ui/button |
||
| return ( | ||
| <div className="flex items-center mb-20 gap-3"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. никаких mb. почитай про схлопывание марджинов |
||
| <Image src={LogoImage} alt="logo" /> | ||
| <h1 className="text-2xl font-bold">TaskTracker Lab</h1> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. это не h1 |
||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| 'use client'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Компонент может работать как серверный |
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Типизация. Пропсы и возвращаемое значение |
||
| export function ScreenshotPlaceholder() { | ||
| return ( | ||
| <> | ||
| <div className="bg-gray-100 rounded-lg h-64 flex items-center justify-center text-gray-400 mt-8"> | ||
| Скриншот интерфейса | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. скриншота тут быть не должно, не обратил внимания что написал комент на весь блок. Соответственно компонент нужно переименовать по смыслу |
||
| </div> | ||
| <p className="text-gray-500 text-sm mt-4">© {new Date().getFullYear()} TaskTracker Lab.</p> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. text-muted-foreground. mt-4 не нужен |
||
| </> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
странная строчка. нужно --color-link: var(--link); и выше в @theme inline