Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/frontend/app/auth/layout.tsx
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>
);
}
9 changes: 9 additions & 0 deletions apps/frontend/src/app/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
--color-sidebar-ring: var(--sidebar-ring);
}

@theme {
--color-link: oklch(54.6% 0.245 262.881);
Copy link
Copy Markdown
Contributor

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

}

:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -119,4 +124,8 @@
body {
@apply bg-background text-foreground;
}
button:not(:disabled),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это в shared/ui/button нужно

[role='button']:not(:disabled) {
cursor: pointer;
}
}
50 changes: 28 additions & 22 deletions apps/frontend/src/pages/login/ui/LoginForm.tsx
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() {
Comment thread
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)}>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем тут кастомный text-red? еще и размер текста

)}
</Field>
<div className="flex items-center justify-between text-sm">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

label все равно переопределяет размер текста. если он нужен, то тогда нужно его только для link задать

<Label className="flex items-center gap-2">
<Checkbox />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нужен в схеме и контролируемый ввод

Запомнить меня
</Label>
<Link href="/auth/reset-password" className="text-link hover:underline">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
);
}
37 changes: 26 additions & 11 deletions apps/frontend/src/pages/login/ui/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
'use client';
Comment thread
kapitulin24 marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Expand Down
20 changes: 15 additions & 5 deletions apps/frontend/src/pages/register/model/registerSchema.ts
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>;
74 changes: 50 additions & 24 deletions apps/frontend/src/pages/register/ui/RegisterForm.tsx
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">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

уже писал про стили

)}
<Button size="lg" className="w-full">
Зарегистрироваться
</Button>
</form>
);
}
37 changes: 34 additions & 3 deletions apps/frontend/src/pages/register/ui/RegisterPage.tsx
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">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Copy link
Copy Markdown
Contributor

@kapitulin24 kapitulin24 Apr 1, 2026

Choose a reason for hiding this comment

The 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>
);
}
5 changes: 5 additions & 0 deletions apps/frontend/src/shared/assets/images/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/frontend/src/shared/assets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as LogoImage } from './images/logo.svg';
11 changes: 11 additions & 0 deletions apps/frontend/src/shared/ui/AppLogo.tsx
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() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

типизация. пропсы и возвращаемое значение

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

это не h1

</div>
);
}
12 changes: 12 additions & 0 deletions apps/frontend/src/shared/ui/ScreenshotPlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Компонент может работать как серверный


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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">
Скриншот интерфейса
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

text-muted-foreground. mt-4 не нужен

</>
);
}
Loading
Loading