initial layout
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export * from "./ui/LoginPage"
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export interface ILoginPageProps {
|
||||
message?: string
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 520px;
|
||||
max-height: 720px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include typography.font-header-l;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
"use client"
|
||||
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { FunctionComponent } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import Link from "next/link"
|
||||
|
||||
import api from "@shared/api"
|
||||
import { useCookie } from "@shared/hooks/useCookie"
|
||||
import {
|
||||
ACCESS_TOKEN_COOKIE,
|
||||
REFRESH_TOKEN_COOKIE,
|
||||
} from "@shared/lib/constants"
|
||||
import { Button, Form, TextField } from "@shared/ui"
|
||||
|
||||
import { ILoginPageProps } from "../model/LoginPage.d"
|
||||
import styles from "./LoginPage.module.scss"
|
||||
|
||||
interface ILoginFormData {
|
||||
login: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export const LoginPage: FunctionComponent<
|
||||
ILoginPageProps
|
||||
> = (): JSX.Element => {
|
||||
const [, setAccessTokenCookie] = useCookie(ACCESS_TOKEN_COOKIE)
|
||||
const [, setRefreshTokenCookie] = useCookie(REFRESH_TOKEN_COOKIE)
|
||||
|
||||
const { mutate, isPending } = api.useMutation("post", "/auth/login", {
|
||||
onSuccess: ({ access, refresh, user }) => {
|
||||
setAccessTokenCookie(access)
|
||||
setRefreshTokenCookie(refresh)
|
||||
console.log("Login successful:", user)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Login failed:", error)
|
||||
},
|
||||
})
|
||||
|
||||
const { register, handleSubmit } = useForm<ILoginFormData>()
|
||||
|
||||
const onSubmit = (data: ILoginFormData): void => {
|
||||
mutate({
|
||||
body: {
|
||||
username: data.login,
|
||||
password: data.password,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root} data-testid="LoginPage">
|
||||
<Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
||||
<h1 className={styles.title}>Вход</h1>
|
||||
<div className={styles.fields}>
|
||||
<TextField
|
||||
id="login"
|
||||
label="Логин"
|
||||
placeholder="Ваш логин"
|
||||
{...register("login")}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label="Пароль"
|
||||
placeholder="Ваш пароль"
|
||||
type="password"
|
||||
{...register("password")}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="primary" disabled={isPending}>
|
||||
Войти
|
||||
</Button>
|
||||
<div className={styles.actions}>
|
||||
<Link className={styles.link} href="/register">
|
||||
Нет аккаунта? Зарегистрироваться
|
||||
</Link>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/RegisterPage"
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface IRegisterPageProps {
|
||||
message?: string
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.root {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.form {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 520px;
|
||||
max-height: 820px;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include typography.font-header-l;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
"use client"
|
||||
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { FunctionComponent } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
|
||||
import { useHookFormMask } from "use-mask-input"
|
||||
|
||||
import api from "@shared/api"
|
||||
import { Button, Form, TextField } from "@shared/ui"
|
||||
|
||||
import { IRegisterPageProps } from "../model/RegisterPage.d"
|
||||
import styles from "./RegisterPage.module.scss"
|
||||
|
||||
interface IRegisterFormData {
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
phone_number?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export const RegisterPage: FunctionComponent<
|
||||
IRegisterPageProps
|
||||
> = (): JSX.Element => {
|
||||
const { mutate, isPending } = api.useMutation("post", "/auth/register", {
|
||||
onSuccess: (data) => {
|
||||
console.log("Register successful:", data)
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Register failed:", error)
|
||||
},
|
||||
})
|
||||
|
||||
const { register, handleSubmit } = useForm<IRegisterFormData>()
|
||||
const registerWithMask = useHookFormMask(register)
|
||||
|
||||
const onSubmit = (data: IRegisterFormData): void => {
|
||||
const phone = data.phone_number?.trim()
|
||||
const avatar = data.avatar?.trim()
|
||||
|
||||
mutate({
|
||||
body: {
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
phone_number: phone || undefined,
|
||||
avatar: avatar || undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root} data-testid="RegisterPage">
|
||||
<Form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
|
||||
<h1 className={styles.title}>Регистрация</h1>
|
||||
<div className={styles.fields}>
|
||||
<TextField
|
||||
id="username"
|
||||
label="Имя пользователя"
|
||||
placeholder="Ваш никнейм"
|
||||
{...register("username")}
|
||||
/>
|
||||
<TextField
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="you@example.com"
|
||||
type="email"
|
||||
{...register("email")}
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
label="Пароль"
|
||||
placeholder="Придумайте пароль"
|
||||
type="password"
|
||||
{...register("password")}
|
||||
/>
|
||||
<TextField
|
||||
id="first_name"
|
||||
label="Имя"
|
||||
placeholder="Ваше имя"
|
||||
{...register("first_name")}
|
||||
/>
|
||||
<TextField
|
||||
id="last_name"
|
||||
label="Фамилия"
|
||||
placeholder="Ваша фамилия"
|
||||
{...register("last_name")}
|
||||
/>
|
||||
<TextField
|
||||
id="phone_number"
|
||||
label="Номер телефона"
|
||||
placeholder="+7 (___) ___-__-__"
|
||||
{...registerWithMask("phone_number", "+7 (999) 999-99-99")}
|
||||
/>
|
||||
<TextField
|
||||
id="avatar"
|
||||
label="Аватар"
|
||||
placeholder="Ссылка на изображение"
|
||||
{...register("avatar")}
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" variant="primary" disabled={isPending}>
|
||||
Создать аккаунт
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user