[frontend]: add eslint

This commit is contained in:
Sergey Krylov 2025-08-03 09:40:49 +03:00
parent 75930d105e
commit a6cef0298d
38 changed files with 1202 additions and 771 deletions

View File

@ -1,19 +1,121 @@
import { dirname } from "path"; import config from 'eslint-config-ksv741';
import { fileURLToPath } from "url"; import nextPlugin from '@next/eslint-plugin-next'
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url); const {flatConfig} = nextPlugin;
const __dirname = dirname(__filename);
const compat = new FlatCompat({ export default [
baseDirectory: __dirname, ...config,
}); flatConfig.coreWebVitals,
{
name: 'my-rules',
files: [
'**/*.js',
'**/*.ts',
'**/*.tsx',
],
rules: {
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'import/order': [
'error', {
pathGroups: [
{
pattern: '__spec__/**',
group: 'builtin',
position: 'before',
},
{
pattern: 'apps/**',
group: 'internal',
position: 'after',
},
{
pattern: 'pages/**',
group: 'internal',
position: 'after',
},
{
pattern: 'widgets/**',
group: 'internal',
position: 'after',
},
{
pattern: 'features/**',
group: 'internal',
position: 'after',
},
{
pattern: 'entities/**',
group: 'internal',
position: 'after',
},
{
pattern: 'shared/**',
group: 'internal',
position: 'after',
},
],
distinctGroup: true,
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'object',
'index',
'type',
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'no-await-in-loop': 'off',
'no-void': ['error', { allowAsStatement: true }],
// "@typescript-eslint/no-extraneous-class": ['error', {allowEmpty: true}],
// "@typescript-eslint/parameter-properties": ['error', { "allow": ["private readonly"] }],
'@typescript-eslint/max-params': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
// todo fixme
'import/max-dependencies': 'off',
'@typescript-eslint/no-unsafe-type-assertion': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'import/no-cycle': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'import/extensions': 'off',
'@typescript-eslint/no-unnecessary-condition': 'off',
'max-classes-per-file': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unnecessary-type-conversion': 'off',
'@typescript-eslint/consistent-return': 'off',
'@typescript-eslint/class-methods-use-this': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/member-ordering': 'off',
'no-undefined': 'off',
'@typescript-eslint/no-misused-spread': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/parameter-properties': 'off',
'@typescript-eslint/no-extraneous-class': 'off',
'@stylistic/max-len': 'off',
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
'import/no-extraneous-dependencies': 'off',
const eslintConfig = [ 'react/destructuring-assignment': ['off', 'never', { ignoreClassFields: true, destructureInSignature: 'always' }],
...compat.extends("next/core-web-vitals", "next/typescript"), "@typescript-eslint/no-misused-promises": ["error", {
"checksVoidReturn": false
}
]
},
},
{ {
ignores: ['src/graphql/generated'] ignores: ['src/graphql/generated']
} }
]; ];
export default eslintConfig;

View File

@ -35,11 +35,13 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "3.3.1", "@eslint/eslintrc": "3.3.1",
"@next/eslint-plugin-next": "15.4.5",
"@tailwindcss/postcss": "4.1.11", "@tailwindcss/postcss": "4.1.11",
"@types/node": "22.17.0", "@types/node": "22.17.0",
"@types/react": "19.1.9", "@types/react": "19.1.9",
"@types/react-dom": "19.1.7", "@types/react-dom": "19.1.7",
"eslint": "9.32.0", "eslint": "9.32.0",
"eslint-config-ksv741": "0.2.0",
"eslint-config-next": "15.4.4", "eslint-config-next": "15.4.4",
"tailwindcss": "4.1.11", "tailwindcss": "4.1.11",
"tw-animate-css": "1.3.6", "tw-animate-css": "1.3.6",

View File

@ -1,20 +1,20 @@
import CreateAccountForm from '@/components/features/auth/forms/CreateAccountForm';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
import React from 'react'; import React from 'react';
import CreateAccountForm from '@/components/features/auth/forms/CreateAccountForm';
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('auth.register'); const t = await getTranslations('auth.register');
return { return {
title: t('heading') title: t('heading'),
} };
} }
const CreateAccountPage = () => { const CreateAccountPage = () => (
return ( <CreateAccountForm />
<CreateAccountForm/> );
);
};
export default CreateAccountPage; export default CreateAccountPage;

View File

@ -1,20 +1,20 @@
import LoginAccountForm from '@/components/features/auth/forms/LoginAccountForm';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
import React from 'react'; import React from 'react';
import LoginAccountForm from '@/components/features/auth/forms/LoginAccountForm';
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('auth.login'); const t = await getTranslations('auth.login');
return { return {
title: t('heading') title: t('heading'),
} };
} }
const LoginAccountPage = () => { const LoginAccountPage = () => (
return ( <LoginAccountForm />
<LoginAccountForm/> );
);
};
export default LoginAccountPage; export default LoginAccountPage;

View File

@ -1,20 +1,20 @@
import { NewPasswordForm } from '@/components/features/auth/forms/NewPasswordForm';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
import React from 'react'; import React from 'react';
import { NewPasswordForm } from '@/components/features/auth/forms/NewPasswordForm';
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('auth.newPassword'); const t = await getTranslations('auth.newPassword');
return { return {
title: t('heading') title: t('heading'),
} };
} }
const NewPasswordPage = () => { const NewPasswordPage = () => (
return ( <NewPasswordForm />
<NewPasswordForm/> );
);
};
export default NewPasswordPage; export default NewPasswordPage;

View File

@ -1,20 +1,20 @@
import { ResetPasswordForm } from '@/components/features/auth/forms/ResetPasswordForm';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
import React from 'react'; import React from 'react';
import { ResetPasswordForm } from '@/components/features/auth/forms/ResetPasswordForm';
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('auth.resetPassword'); const t = await getTranslations('auth.resetPassword');
return { return {
title: t('heading') title: t('heading'),
} };
} }
const ResetPasswordPage = () => { const ResetPasswordPage = () => (
return ( <ResetPasswordForm />
<ResetPasswordForm/> );
);
};
export default ResetPasswordPage; export default ResetPasswordPage;

View File

@ -1,27 +1,28 @@
import { VerifyAccountForm } from '@/components/features/auth/forms/VerifyAccoiuntForm';
import { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { getTranslations } from 'next-intl/server';
import React from 'react'; import React from 'react';
import { VerifyAccountForm } from '@/components/features/auth/forms/VerifyAccoiuntForm';
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> { export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('auth.verify'); const t = await getTranslations('auth.verify');
return { return {
title: t('heading') title: t('heading'),
} };
} }
type VerifyAccountPageProps = { type VerifyAccountPageProps = {
searchParams: Promise<{token: string}> searchParams: Promise<{ token: string }>;
} };
const VerifyAccountPage = async (props: VerifyAccountPageProps) => { const VerifyAccountPage = async (props: VerifyAccountPageProps) => {
const {token} = await props.searchParams; const { token } = await props.searchParams;
if (!token) { if (!token) {
return redirect('/account/create') return redirect('/account/create');
} }
return ( return (

View File

@ -1,42 +1,46 @@
import { Geist } from 'next/font/google';
import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';
import ApolloClientProvider from '@/providers/ApolloClientProvider'; import ApolloClientProvider from '@/providers/ApolloClientProvider';
import { ThemeProvider } from '@/providers/ThemeProvider'; import { ThemeProvider } from '@/providers/ThemeProvider';
import { ToastProvider } from '@/providers/ToastProvider'; import { ToastProvider } from '@/providers/ToastProvider';
import type { Metadata } from "next";
import { NextIntlClientProvider } from 'next-intl'; import '../styles/globals.css';
import { getLocale, getMessages } from 'next-intl/server'; import type { Metadata } from 'next';
import { Geist } from "next/font/google"; import type { ReactNode } from 'react';
import "../styles/globals.css";
const geistSans = Geist({ const geistSans = Geist({
variable: "--font-geist-sans", variable: '--font-geist-sans',
subsets: ["latin"], subsets: ['latin'],
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: 'Create Next App',
description: "Generated by create next app", description: 'Generated by create next app',
}; };
export default async function RootLayout({ const RootLayout = async ({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: ReactNode;
}>) { }>) => {
const locale = await getLocale() const locale = await getLocale();
const messages = await getMessages() const messages = await getMessages();
return ( return (
<html lang={locale} suppressHydrationWarning> <html suppressHydrationWarning lang={locale}>
<body className={geistSans.variable}> <body className={geistSans.variable}>
<ApolloClientProvider> <ApolloClientProvider>
<NextIntlClientProvider messages={messages}> <NextIntlClientProvider messages={messages}>
<ThemeProvider <ThemeProvider
disableTransitionOnChange
enableSystem
attribute="class" attribute="class"
defaultTheme="dark" defaultTheme="dark"
enableSystem
disableTransitionOnChange
> >
<ToastProvider/> <ToastProvider />
{children} {children}
</ThemeProvider> </ThemeProvider>
</NextIntlClientProvider> </NextIntlClientProvider>
@ -44,4 +48,6 @@ export default async function RootLayout({
</body> </body>
</html> </html>
); );
} };
export default RootLayout;

View File

@ -1,4 +1,4 @@
'use client' 'use client';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';

View File

@ -1,34 +1,50 @@
import Link from 'next/link';
import React from 'react';
import { LogoImage } from '@/components/images/LogoImage'; import { LogoImage } from '@/components/images/LogoImage';
import { Button } from '@/components/ui/common/Button'; import { Button } from '@/components/ui/common/Button';
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/common/Card'; import {
import Link from 'next/link'; Card, CardContent, CardFooter, CardHeader, CardTitle,
import React, { FC, ReactNode } from 'react'; } from '@/components/ui/common/Card';
import type { FC, ReactNode } from 'react';
type AuthWrapperProps = { type AuthWrapperProps = {
heading: string heading: string;
backButtonLabel?: string backButtonLabel?: string;
backButtonHref?: string backButtonHref?: string;
children: ReactNode children: ReactNode;
} };
const AuthWrapper: FC<AuthWrapperProps> = (props) => { const AuthWrapper: FC<AuthWrapperProps> = (props) => {
const { children, backButtonHref, backButtonLabel, heading } = props; const {
children, backButtonHref, backButtonLabel, heading,
} = props;
return ( return (
<div className='flex h-full items-center justify-center'> <div className="flex h-full items-center justify-center">
<Card className='w-[450px]'> <Card className="w-[450px]">
<CardHeader className='flex-row items-center justify-center gap-x-4'> <CardHeader className="flex-row items-center justify-center gap-x-4">
<LogoImage /> <LogoImage />
<CardTitle>{heading}</CardTitle>
<CardTitle>
{heading}
</CardTitle>
</CardHeader> </CardHeader>
<CardContent>{children}</CardContent>
<CardFooter className='-mt-2'> <CardContent>
{backButtonLabel && backButtonHref && ( {children}
<Link href={backButtonHref} className='w-full'> </CardContent>
<Button variant='ghost' className='w-full'>
{backButtonLabel} <CardFooter className="-mt-2">
</Button> {backButtonLabel && backButtonHref
</Link> ? (
)} <Link className="w-full" href={backButtonHref}>
<Button className="w-full" variant="ghost">
{backButtonLabel}
</Button>
</Link>
)
: null}
</CardFooter> </CardFooter>
</Card> </Card>
</div> </div>

View File

@ -1,134 +1,160 @@
'use client' 'use client';
import { Button } from '@/components/ui/common/Button';
import { createAccountSchema, TypeCreateAccountSchema } from '@/schemas/auth/create-account.schema';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { CircleCheck } from 'lucide-react'; import { CircleCheck } from 'lucide-react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import React, { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import AuthWrapper from '../AuthWrapper';
import React, { useState } from 'react';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel } from '@/components/ui/common/Form';
import { Input } from '@/components/ui/common/Input';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/common/Alert';
import { useCreateUserMutation } from '@/graphql/generated/output';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/common/Alert';
import { Button } from '@/components/ui/common/Button';
import {
Form, FormControl, FormDescription, FormField, FormItem, FormLabel,
} from '@/components/ui/common/Form';
import { Input } from '@/components/ui/common/Input';
import { useCreateUserMutation } from '@/graphql/generated/output';
import { createAccountSchema } from '@/schemas/auth/create-account.schema';
import AuthWrapper from '../AuthWrapper';
import type { TypeCreateAccountSchema } from '@/schemas/auth/create-account.schema';
const CreateAccountForm = () => { const CreateAccountForm = () => {
const [isSuccess, setIsSuccess] = useState(false) const [isSuccess, setIsSuccess] = useState(false);
const t = useTranslations('auth.register') const t = useTranslations('auth.register');
const form = useForm<TypeCreateAccountSchema>({ const form = useForm<TypeCreateAccountSchema>({
resolver: zodResolver(createAccountSchema), resolver: zodResolver(createAccountSchema),
defaultValues: { defaultValues: {
name: '', name: '',
email: '', email: '',
password: '' password: '',
} },
}) });
const { isValid } = form.formState const { isValid } = form.formState;
const [create, { loading: isLoadingCreate }] = useCreateUserMutation({ const [create, { loading: isLoadingCreate }] = useCreateUserMutation({
onCompleted() { onCompleted() {
setIsSuccess(true) setIsSuccess(true);
}, },
onError() { onError() {
toast.error(t('errorMessage')) toast.error(t('errorMessage'));
} },
}) });
function onSubmit(data: TypeCreateAccountSchema) { function onSubmit(data: TypeCreateAccountSchema) {
create({ variables: { data } }) void create({ variables: { data } });
} }
return ( return (
<AuthWrapper <AuthWrapper
heading={t('heading')} backButtonHref="/account/login"
backButtonLabel={t('backButtonLabel')} backButtonLabel={t('backButtonLabel')}
backButtonHref='/account/login' heading={t('heading')}
> >
{isSuccess ? ( {isSuccess
<Alert> ? (
<CircleCheck className='size-4' /> <Alert>
<AlertTitle>{t('successAlertTitle')}</AlertTitle> <CircleCheck className="size-4" />
<AlertDescription>
{t('successAlertDescription')} <AlertTitle>
</AlertDescription> {t('successAlertTitle')}
</Alert> </AlertTitle>
) : (
<Form {...form}> <AlertDescription>
<form {t('successAlertDescription')}
onSubmit={form.handleSubmit(onSubmit)} </AlertDescription>
className='grid gap-y-3' </Alert>
> )
<FormField : (
control={form.control} <Form {...form}>
name='name' <form
render={({ field }) => ( className="grid gap-y-3"
<FormItem> onSubmit={form.handleSubmit(onSubmit)}
<FormLabel>{t('usernameLabel')}</FormLabel>
<FormControl>
<Input
placeholder='johndoe'
disabled={isLoadingCreate}
{...field}
/>
</FormControl>
<FormDescription>
{t('usernameDescription')}
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>{t('emailLabel')}</FormLabel>
<FormControl>
<Input
placeholder='john.doe@example.com'
disabled={isLoadingCreate}
{...field}
/>
</FormControl>
<FormDescription>
{t('emailDescription')}
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel>{t('passwordLabel')}</FormLabel>
<FormControl>
<Input
placeholder='********'
type='password'
disabled={isLoadingCreate}
{...field}
/>
</FormControl>
<FormDescription>
{t('passwordDescription')}
</FormDescription>
</FormItem>
)}
/>
<Button
className='mt-2 w-full'
disabled={!isValid || isLoadingCreate}
> >
{t('submitButton')} <FormField
</Button> control={form.control}
</form> name="name"
</Form> render={({ field }) => (
)} <FormItem>
<FormLabel>
{t('usernameLabel')}
</FormLabel>
<FormControl>
<Input
disabled={isLoadingCreate}
placeholder="johndoe"
{...field}
/>
</FormControl>
<FormDescription>
{t('usernameDescription')}
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
{t('emailLabel')}
</FormLabel>
<FormControl>
<Input
disabled={isLoadingCreate}
placeholder="john.doe@example.com"
{...field}
/>
</FormControl>
<FormDescription>
{t('emailDescription')}
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>
{t('passwordLabel')}
</FormLabel>
<FormControl>
<Input
disabled={isLoadingCreate}
placeholder="********"
type="password"
{...field}
/>
</FormControl>
<FormDescription>
{t('passwordDescription')}
</FormDescription>
</FormItem>
)}
/>
<Button
className="mt-2 w-full"
disabled={!isValid || isLoadingCreate}
>
{t('submitButton')}
</Button>
</form>
</Form>
)}
</AuthWrapper> </AuthWrapper>
); );
}; };

View File

@ -1,24 +1,29 @@
'use client' 'use client';
import { Button } from '@/components/ui/common/Button';
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel } from '@/components/ui/common/Form';
import { Input } from '@/components/ui/common/Input';
import { useLoginUserMutation } from '@/graphql/generated/output';
import { loginSchema, TypeLoginSchema } from '@/schemas/auth/login.schema';
import { useTranslations } from 'next-intl';
import { zodResolver } from '@hookform/resolvers/zod';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import AuthWrapper from '../AuthWrapper';
import { zodResolver } from '@hookform/resolvers/zod'; import { Button } from '@/components/ui/common/Button';
import {
Form, FormControl, FormDescription, FormField, FormItem, FormLabel,
} from '@/components/ui/common/Form';
import { Input } from '@/components/ui/common/Input';
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/common/InputOTP'; import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/common/InputOTP';
import { useLoginUserMutation } from '@/graphql/generated/output';
import { loginSchema } from '@/schemas/auth/login.schema';
import AuthWrapper from '../AuthWrapper';
import type { TypeLoginSchema } from '@/schemas/auth/login.schema';
const LoginAccountForm = () => { const LoginAccountForm = () => {
const t = useTranslations('auth.login') const t = useTranslations('auth.login');
const [isShowTwoFactor, setIsShowTwoFactor] = useState(false) const [isShowTwoFactor, setIsShowTwoFactor] = useState(false);
const router = useRouter(); const router = useRouter();
const form = useForm<TypeLoginSchema>({ const form = useForm<TypeLoginSchema>({
@ -26,119 +31,139 @@ const LoginAccountForm = () => {
defaultValues: { defaultValues: {
login: '', login: '',
password: '', password: '',
} },
}) });
const { isValid } = form.formState const { isValid } = form.formState;
const [login, {loading: isLoadingLogin}] = useLoginUserMutation({ const [login, { loading: isLoadingLogin }] = useLoginUserMutation({
onCompleted(data) { onCompleted(data) {
if (data.loginUser.message) { if (data.loginUser.message) {
setIsShowTwoFactor(true) setIsShowTwoFactor(true);
} else { } else {
toast.success(t('successMessage')) toast.success(t('successMessage'));
router.push('/dashboard/settings') router.push('/dashboard/settings');
} }
}, },
onError: () => { onError: () => {
toast.error(t('errorMessage')) toast.error(t('errorMessage'));
} },
}) });
function onSubmit(data: TypeLoginSchema) { function onSubmit(data: TypeLoginSchema) {
void login({variables: {data}}) void login({ variables: { data } });
} }
return ( return (
<AuthWrapper <AuthWrapper
heading={t('heading')} backButtonHref="/account/create"
backButtonLabel={t('backButtonLabel')} backButtonLabel={t('backButtonLabel')}
backButtonHref='/account/create' heading={t('heading')}
> >
<Form {...form}> <Form {...form}>
<form <form
className="grid gap-y-3"
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className='grid gap-y-3'
> >
{isShowTwoFactor ? ( {isShowTwoFactor
<FormField ? (
control={form.control}
name='pin'
render={({ field }) => (
<FormItem>
<FormLabel>{t('pinLabel')}</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>
<FormDescription>
{t('pinDescription')}
</FormDescription>
</FormItem>
)}
/>
) : (
<>
<FormField <FormField
control={form.control} control={form.control}
name='login' name="pin"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('loginLabel')}</FormLabel> <FormLabel>
{t('pinLabel')}
</FormLabel>
<FormControl> <FormControl>
<Input <InputOTP maxLength={6} {...field}>
placeholder='johndoe' <InputOTPGroup>
disabled={isLoadingLogin} <InputOTPSlot index={0} />
{...field}
/> <InputOTPSlot index={1} />
<InputOTPSlot index={2} />
<InputOTPSlot index={3} />
<InputOTPSlot index={4} />
<InputOTPSlot index={5} />
</InputOTPGroup>
</InputOTP>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{t('loginDescription')} {t('pinDescription')}
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
/> />
<FormField )
control={form.control} : (
name='password' <>
render={({ field }) => ( <FormField
<FormItem> control={form.control}
<div className='flex items-center justify-between'> name="login"
render={({ field }) => (
<FormItem>
<FormLabel> <FormLabel>
{t('passwordLabel')} {t('loginLabel')}
</FormLabel> </FormLabel>
<Link
href='/account/recovery' <FormControl>
className='ml-auto inline-block text-sm' <Input
> disabled={isLoadingLogin}
{t('forgotPassword')} placeholder="johndoe"
</Link> {...field}
</div> />
<FormControl> </FormControl>
<Input
placeholder='********' <FormDescription>
type='password' {t('loginDescription')}
disabled={isLoadingLogin} </FormDescription>
{...field} </FormItem>
/> )}
</FormControl> />
<FormDescription>
{t('passwordDescription')} <FormField
</FormDescription> control={form.control}
</FormItem> name="password"
)} render={({ field }) => (
/> <FormItem>
</> <div className="flex items-center justify-between">
)} <FormLabel>
{t('passwordLabel')}
</FormLabel>
<Link
className="ml-auto inline-block text-sm"
href="/account/recovery"
>
{t('forgotPassword')}
</Link>
</div>
<FormControl>
<Input
disabled={isLoadingLogin}
placeholder="********"
type="password"
{...field}
/>
</FormControl>
<FormDescription>
{t('passwordDescription')}
</FormDescription>
</FormItem>
)}
/>
</>
)}
<Button <Button
className='mt-2 w-full' className="mt-2 w-full"
disabled={!isValid || isLoadingLogin} disabled={!isValid || isLoadingLogin}
> >
{t('submitButton')} {t('submitButton')}

View File

@ -1,113 +1,121 @@
'use client' 'use client';
import { zodResolver } from '@hookform/resolvers/zod' import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslations } from 'next-intl' import { useParams, useRouter } from 'next/navigation';
import { useParams, useRouter } from 'next/navigation' import { useTranslations } from 'next-intl';
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form';
import { toast } from 'sonner' import { toast } from 'sonner';
import { Button } from '@/components/ui/common/Button' import { Button } from '@/components/ui/common/Button';
import { import {
Form, Form,
FormControl, FormControl,
FormDescription, FormDescription,
FormField, FormField,
FormItem, FormItem,
FormLabel FormLabel,
} from '@/components/ui/common/Form' } from '@/components/ui/common/Form';
import { Input } from '@/components/ui/common/Input' import { Input } from '@/components/ui/common/Input';
import { useNewPasswordMutation } from '@/graphql/generated/output';
import { newPasswordSchema } from '@/schemas/auth/new-password.schema';
import { useNewPasswordMutation } from '@/graphql/generated/output' import AuthWrapper from '../AuthWrapper';
import type { TypeNewPasswordSchema } from '@/schemas/auth/new-password.schema';
import AuthWrapper from '../AuthWrapper' export const NewPasswordForm = () => {
import { newPasswordSchema, TypeNewPasswordSchema } from '@/schemas/auth/new-password.schema' const t = useTranslations('auth.newPassword');
export function NewPasswordForm() { const router = useRouter();
const t = useTranslations('auth.newPassword') const params = useParams<{ token: string }>();
const router = useRouter()
const params = useParams<{ token: string }>()
const form = useForm<TypeNewPasswordSchema>({ const form = useForm<TypeNewPasswordSchema>({
resolver: zodResolver(newPasswordSchema), resolver: zodResolver(newPasswordSchema),
defaultValues: { defaultValues: {
password: '', password: '',
passwordRepeat: '' passwordRepeat: '',
} },
}) });
const [newPassword, { loading: isLoadingNew }] = useNewPasswordMutation({ const [newPassword, { loading: isLoadingNew }] = useNewPasswordMutation({
onCompleted(data) { onCompleted(data) {
toast.success(t('successMessage')) toast.success(t('successMessage'));
router.push('/account/login') router.push('/account/login');
}, },
onError() { onError() {
toast.error(t('errorMessage')) toast.error(t('errorMessage'));
} },
}) });
const { isValid } = form.formState const { isValid } = form.formState;
function onSubmit(data: TypeNewPasswordSchema) { function onSubmit(data: TypeNewPasswordSchema) {
newPassword({ variables: { data: { ...data, token: params.token } } }) void newPassword({ variables: { data: { ...data, token: params.token } } });
} }
return ( return (
<AuthWrapper <AuthWrapper
heading={t('heading')} backButtonHref="/account/login"
backButtonLabel={t('backButtonLabel')} backButtonLabel={t('backButtonLabel')}
backButtonHref='/account/login' heading={t('heading')}
> >
<Form {...form}> <Form {...form}>
<form <form
className="grid gap-y-3"
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className='grid gap-y-3'
> >
<FormField <FormField
control={form.control} control={form.control}
name='password' name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('passwordLabel')}</FormLabel> <FormLabel>
{t('passwordLabel')}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder='********'
type='password'
disabled={isLoadingNew} disabled={isLoadingNew}
placeholder="********"
type="password"
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{t('passwordDescription')} {t('passwordDescription')}
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name='passwordRepeat' name="passwordRepeat"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{t('passwordRepeatLabel')} {t('passwordRepeatLabel')}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input <Input
placeholder='********'
type='password'
disabled={isLoadingNew} disabled={isLoadingNew}
placeholder="********"
type="password"
{...field} {...field}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{t('passwordRepeatDescription')} {t('passwordRepeatDescription')}
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
/> />
<Button <Button
className='mt-2 w-full' className="mt-2 w-full"
disabled={!isValid || isLoadingNew} disabled={!isValid || isLoadingNew}
> >
{t('submitButton')} {t('submitButton')}
@ -115,5 +123,5 @@ export function NewPasswordForm() {
</form> </form>
</Form> </Form>
</AuthWrapper> </AuthWrapper>
) );
} };

View File

@ -1,113 +1,123 @@
'use client' 'use client';
import { zodResolver } from '@hookform/resolvers/zod' import { zodResolver } from '@hookform/resolvers/zod';
import { CircleCheck } from 'lucide-react' import { CircleCheck } from 'lucide-react';
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl';
import { useState } from 'react' import { useState } from 'react';
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form';
import { toast } from 'sonner' import { toast } from 'sonner';
import { import {
Alert, Alert,
AlertDescription, AlertDescription,
AlertTitle AlertTitle,
} from '@/components/ui/common/Alert' } from '@/components/ui/common/Alert';
import { Button } from '@/components/ui/common/Button' import { Button } from '@/components/ui/common/Button';
import { import {
Form, Form,
FormControl, FormControl,
FormDescription, FormDescription,
FormField, FormField,
FormItem, FormItem,
FormLabel FormLabel,
} from '@/components/ui/common/Form' } from '@/components/ui/common/Form';
import { Input } from '@/components/ui/common/Input' import { Input } from '@/components/ui/common/Input';
import { useResetPasswordMutation } from '@/graphql/generated/output';
import { useResetPasswordMutation } from '@/graphql/generated/output'
import { import {
type TypeResetPasswordSchema, type TypeResetPasswordSchema,
resetPasswordSchema resetPasswordSchema,
} from '@/schemas/auth/reset-password.schema' } from '@/schemas/auth/reset-password.schema';
import AuthWrapper from '../AuthWrapper' import AuthWrapper from '../AuthWrapper';
export function ResetPasswordForm() { export const ResetPasswordForm = () => {
const t = useTranslations('auth.resetPassword') const t = useTranslations('auth.resetPassword');
const [isSuccess, setIsSuccess] = useState(false) const [isSuccess, setIsSuccess] = useState(false);
const form = useForm<TypeResetPasswordSchema>({ const form = useForm<TypeResetPasswordSchema>({
resolver: zodResolver(resetPasswordSchema), resolver: zodResolver(resetPasswordSchema),
defaultValues: { defaultValues: {
email: '' email: '',
} },
}) });
const [resetPassword, { loading: isLoadingReset }] = const [resetPassword, { loading: isLoadingReset }] = useResetPasswordMutation({
useResetPasswordMutation({ onCompleted() {
onCompleted() { setIsSuccess(true);
setIsSuccess(true) },
}, onError() {
onError() { toast.error(t('errorMessage'));
toast.error(t('errorMessage')) },
} });
})
const { isValid } = form.formState const { isValid } = form.formState;
function onSubmit(data: TypeResetPasswordSchema) { function onSubmit(data: TypeResetPasswordSchema) {
void resetPassword({ variables: { data } }) void resetPassword({ variables: { data } });
} }
return ( return (
<AuthWrapper <AuthWrapper
heading={t('heading')} backButtonHref="/account/login"
backButtonLabel={t('backButtonLabel')} backButtonLabel={t('backButtonLabel')}
backButtonHref='/account/login' heading={t('heading')}
> >
{isSuccess ? ( {isSuccess
<Alert> ? (
<CircleCheck className='size-4' /> <Alert>
<AlertTitle>{t('successAlertTitle')}</AlertTitle> <CircleCheck className="size-4" />
<AlertDescription>
{t('successAlertDescription')} <AlertTitle>
</AlertDescription> {t('successAlertTitle')}
</Alert> </AlertTitle>
) : (
<Form {...form}> <AlertDescription>
<form {t('successAlertDescription')}
onSubmit={form.handleSubmit(onSubmit)} </AlertDescription>
className='grid gap-y-3' </Alert>
> )
<FormField : (
control={form.control} <Form {...form}>
name='email' <form
render={({ field }) => ( className="grid gap-y-3"
<FormItem> onSubmit={() => {
<FormLabel>{t('emailLabel')}</FormLabel> form.handleSubmit(onSubmit);
<FormControl> }}
<Input
placeholder='john.doe@example.com'
disabled={isLoadingReset}
{...field}
/>
</FormControl>
<FormDescription>
{t('emailDescription')}
</FormDescription>
</FormItem>
)}
/>
<Button
className='mt-2 w-full'
disabled={!isValid || isLoadingReset}
> >
{t('submitButton')} <FormField
</Button> control={form.control}
</form> name="email"
</Form> render={({ field }) => (
)} <FormItem>
<FormLabel>
{t('emailLabel')}
</FormLabel>
<FormControl>
<Input
disabled={isLoadingReset}
placeholder="john.doe@example.com"
{...field}
/>
</FormControl>
<FormDescription>
{t('emailDescription')}
</FormDescription>
</FormItem>
)}
/>
<Button
className="mt-2 w-full"
disabled={!isValid || isLoadingReset}
>
{t('submitButton')}
</Button>
</form>
</Form>
)}
</AuthWrapper> </AuthWrapper>
) );
} };

View File

@ -1,46 +1,47 @@
'use client' 'use client';
import { Loader } from 'lucide-react' import { Loader } from 'lucide-react';
import { useTranslations } from 'next-intl' import { useRouter } from 'next/navigation';
import { useRouter } from 'next/navigation' import { useTranslations } from 'next-intl';
import { useEffect } from 'react' import { useEffect } from 'react';
import { toast } from 'sonner' import { toast } from 'sonner';
import { useVerifyAccountMutation } from '@/graphql/generated/output' import { useVerifyAccountMutation } from '@/graphql/generated/output';
import AuthWrapper from '../AuthWrapper';
import AuthWrapper from '../AuthWrapper'
type VerifyAccountFormProps = { type VerifyAccountFormProps = {
token: string token: string;
} };
export function VerifyAccountForm(props: VerifyAccountFormProps) { export const VerifyAccountForm = (props: VerifyAccountFormProps) => {
const { token } = props; const { token } = props;
const t = useTranslations('auth.verify') const t = useTranslations('auth.verify');
const router = useRouter() const router = useRouter();
const [verify] = useVerifyAccountMutation({ const [verify] = useVerifyAccountMutation({
onCompleted() { onCompleted() {
toast.success(t('successMessage')) toast.success(t('successMessage'));
router.push('/dashboard/settings') router.push('/dashboard/settings');
}, },
onError() { onError() {
toast.error(t('errorMessage')) toast.error(t('errorMessage'));
} },
}) });
useEffect(() => { useEffect(() => {
void verify({ void verify({
variables: { variables: {
data: { token } data: { token },
} },
}) });
}, [token, verify]) }, [token, verify]);
return ( return (
<AuthWrapper heading={t('heading')}> <AuthWrapper heading={t('heading')}>
<div className='flex justify-center'> <div className="flex justify-center">
<Loader className='size-8 animate-spin' /> <Loader className="size-8 animate-spin" />
</div> </div>
</AuthWrapper> </AuthWrapper>
) );
} };

View File

@ -1,43 +1,44 @@
export function LogoImage() { export const LogoImage = () => (
return ( <svg
<svg height={42}
xmlns='http://www.w3.org/2000/svg' viewBox="0 0 2400 2800"
xmlnsXlink='http://www.w3.org/1999/xlink' width={42}
viewBox='0 0 2400 2800' xmlns="http://www.w3.org/2000/svg"
xmlSpace='preserve' xmlnsXlink="http://www.w3.org/1999/xlink"
width={42} xmlSpace="preserve"
height={42} >
> <title>Logo</title>
<title>Logo</title>
<g>
<polygon
className="fill-white"
points="2200,1300 1800,1700 1400,1700 1050,2050 1050,1700 600,1700 600,200 2200,200"
/>
<g> <g>
<polygon <g id="Layer_1-2">
className='fill-white' <path
points='2200,1300 1800,1700 1400,1700 1050,2050 1050,1700 600,1700 600,200 2200,200 ' className="fill-primary"
/> d="M500,0L0,500v1800h600v500l500-500h400l900-900V0H500z M2200,1300l-400,400h-400l-350,350v-350H600V200h1600 V1300z"
<g> />
<g id='Layer_1-2'>
<path <rect
className='fill-primary' className="fill-primary"
d='M500,0L0,500v1800h600v500l500-500h400l900-900V0H500z M2200,1300l-400,400h-400l-350,350v-350H600V200h1600 height="600"
V1300z' width="200"
/> x="1700"
<rect y="550"
x='1700' />
y='550'
className='fill-primary' <rect
width='200' className="fill-primary"
height='600' height="600"
/> width="200"
<rect x="1150"
x='1150' y="550"
y='550' />
className='fill-primary'
width='200'
height='600'
/>
</g>
</g> </g>
</g> </g>
</svg> </g>
) </svg>
} );

View File

@ -1,7 +1,7 @@
import { cn } from '@/utils/twMerge' import { type VariantProps, cva } from 'class-variance-authority';
import { type VariantProps, cva } from 'class-variance-authority' import { type HTMLAttributes, forwardRef } from 'react';
import { type HTMLAttributes, forwardRef } from 'react'
import { cn } from '@/utils/twMerge';
const alertVariants = cva( const alertVariants = cva(
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
@ -10,14 +10,14 @@ const alertVariants = cva(
variant: { variant: {
default: 'bg-background text-foreground', default: 'bg-background text-foreground',
destructive: destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive' 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
} },
}, },
defaultVariants: { defaultVariants: {
variant: 'default' variant: 'default',
} },
} },
) );
const Alert = forwardRef< const Alert = forwardRef<
HTMLDivElement, HTMLDivElement,
@ -25,24 +25,25 @@ const Alert = forwardRef<
>(({ className, variant, ...props }, ref) => ( >(({ className, variant, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
role='alert'
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
role="alert"
{...props} {...props}
/> />
)) ));
Alert.displayName = 'Alert' Alert.displayName = 'Alert';
const AlertTitle = forwardRef< const AlertTitle = forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
HTMLAttributes<HTMLHeadingElement> HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
// eslint-disable-next-line jsx-a11y/heading-has-content
<h5 <h5
ref={ref} ref={ref}
className={cn('mb-1 font-medium leading-none tracking-wide', className)} className={cn('mb-1 font-medium leading-none tracking-wide', className)}
{...props} {...props}
/> />
)) ));
AlertTitle.displayName = 'AlertTitle' AlertTitle.displayName = 'AlertTitle';
const AlertDescription = forwardRef< const AlertDescription = forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@ -52,11 +53,11 @@ const AlertDescription = forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
'mt-2 text-sm text-muted-foreground [&_p]:leading-relaxed', 'mt-2 text-sm text-muted-foreground [&_p]:leading-relaxed',
className className,
)} )}
{...props} {...props}
/> />
)) ));
AlertDescription.displayName = 'AlertDescription' AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertDescription, AlertTitle } export { Alert, AlertDescription, AlertTitle };

View File

@ -1,8 +1,8 @@
import { cn } from '@/utils/twMerge' import { Slot } from '@radix-ui/react-slot';
import { Slot } from '@radix-ui/react-slot' import { type VariantProps, cva } from 'class-variance-authority';
import { type VariantProps, cva } from 'class-variance-authority' import { type ButtonHTMLAttributes, forwardRef } from 'react';
import { type ButtonHTMLAttributes, forwardRef } from 'react'
import { cn } from '@/utils/twMerge';
const buttonVariants = cva( const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
@ -12,39 +12,40 @@ const buttonVariants = cva(
default: 'bg-primary text-primary-foreground', default: 'bg-primary text-primary-foreground',
outline: 'border border-border bg-background', outline: 'border border-border bg-background',
secondary: 'bg-secondary text-secondary-foreground', secondary: 'bg-secondary text-secondary-foreground',
ghost: 'text-accent-foreground hover:bg-accent hover:text-accent-foreground' ghost: 'text-accent-foreground hover:bg-accent hover:text-accent-foreground',
}, },
size: { size: {
default: 'h-10 px-5 py-2 rounded-full', default: 'h-10 px-5 py-2 rounded-full',
icon: 'size-8 rounded-full', icon: 'size-8 rounded-full',
lgIcon: 'size-10 rounded-full' lgIcon: 'size-10 rounded-full',
} },
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: 'default',
size: 'default' size: 'default',
} },
} },
) );
export interface ButtonProps export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> & {
extends ButtonHTMLAttributes<HTMLButtonElement>, asChild?: boolean;
VariantProps<typeof buttonVariants> { };
asChild?: boolean
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>( const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({
const Comp = asChild ? Slot : 'button' className, variant, size, asChild = false, ...props
}, ref) => {
const Comp = asChild ? Slot : 'button';
return ( return (
<Comp <Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref} ref={ref}
className={cn(buttonVariants({ variant, size, className }))}
{...props} {...props}
/> />
) );
} },
) );
Button.displayName = 'Button' Button.displayName = 'Button';
export { Button, buttonVariants } export { Button, buttonVariants };

View File

@ -1,6 +1,6 @@
import { cn } from '@/utils/twMerge'; import { type HTMLAttributes, forwardRef } from 'react';
import { type HTMLAttributes, forwardRef } from 'react'
import { cn } from '@/utils/twMerge';
const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
@ -8,13 +8,13 @@ const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
ref={ref} ref={ref}
className={cn( className={cn(
'bg-card text-card-foreground border-border rounded-lg border shadow-sm', 'bg-card text-card-foreground border-border rounded-lg border shadow-sm',
className className,
)} )}
{...props} {...props}
/> />
) ),
) );
Card.displayName = 'Card' Card.displayName = 'Card';
const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
@ -23,9 +23,9 @@ const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
className={cn('flex flex-col space-y-1.5 p-6', className)} className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props} {...props}
/> />
) ),
) );
CardHeader.displayName = 'CardHeader' CardHeader.displayName = 'CardHeader';
const CardTitle = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( const CardTitle = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
@ -33,13 +33,13 @@ const CardTitle = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
ref={ref} ref={ref}
className={cn( className={cn(
'text-2xl font-semibold leading-none tracking-wide', 'text-2xl font-semibold leading-none tracking-wide',
className className,
)} )}
{...props} {...props}
/> />
) ),
) );
CardTitle.displayName = 'CardTitle' CardTitle.displayName = 'CardTitle';
const CardDescription = forwardRef< const CardDescription = forwardRef<
HTMLDivElement, HTMLDivElement,
@ -50,15 +50,15 @@ const CardDescription = forwardRef<
className={cn('text-muted-foreground text-sm', className)} className={cn('text-muted-foreground text-sm', className)}
{...props} {...props}
/> />
)) ));
CardDescription.displayName = 'CardDescription' CardDescription.displayName = 'CardDescription';
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} /> <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
) ),
) );
CardContent.displayName = 'CardContent' CardContent.displayName = 'CardContent';
const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
@ -67,8 +67,10 @@ const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
className={cn('flex items-center p-6 pt-0', className)} className={cn('flex items-center p-6 pt-0', className)}
{...props} {...props}
/> />
) ),
) );
CardFooter.displayName = 'CardFooter' CardFooter.displayName = 'CardFooter';
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } export {
Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,
};

View File

@ -1,8 +1,6 @@
'use client' 'use client';
import { cn } from '@/utils/twMerge'; import { Slot } from '@radix-ui/react-slot';
import * as LabelPrimitive from '@radix-ui/react-label'
import { Slot } from '@radix-ui/react-slot'
import { import {
type ComponentPropsWithoutRef, type ComponentPropsWithoutRef,
type ComponentRef, type ComponentRef,
@ -10,82 +8,62 @@ import {
createContext, createContext,
forwardRef, forwardRef,
useContext, useContext,
useId useId,
} from 'react' } from 'react';
import { import {
Controller, Controller,
type ControllerProps, type ControllerProps,
type FieldPath, type FieldPath,
type FieldValues, type FieldValues,
FormProvider, FormProvider,
useFormContext useFormContext,
} from 'react-hook-form' } from 'react-hook-form';
import { cn } from '@/utils/twMerge';
import { Label } from './Label' import { Label } from './Label';
const Form = FormProvider import type * as LabelPrimitive from '@radix-ui/react-label';
const Form = FormProvider;
type FormFieldContextValue< type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = { > = {
name: TName name: TName;
} };
const FormFieldContext = createContext<FormFieldContextValue>( const FormFieldContext = createContext<FormFieldContextValue>(
{} as FormFieldContextValue {} as FormFieldContextValue,
) );
const FormField = < const FormField = <
TFieldValues extends FieldValues = FieldValues, TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({ >({
...props ...props
}: ControllerProps<TFieldValues, TName>) => { }: ControllerProps<TFieldValues, TName>) => (
return ( // eslint-disable-next-line react/jsx-no-constructed-context-values
<FormFieldContext.Provider value={{ name: props.name }}> <FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} /> <Controller {...props} />
</FormFieldContext.Provider> </FormFieldContext.Provider>
) );
}
const useFormField = () => {
const fieldContext = useContext(FormFieldContext)
const itemContext = useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>')
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState
}
}
type FormItemContextValue = { type FormItemContextValue = {
id: string id: string;
} };
const FormItemContext = createContext<FormItemContextValue>( const FormItemContext = createContext<FormItemContextValue>(
{} as FormItemContextValue {} as FormItemContextValue,
) );
const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>( const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => { ({ className, ...props }, ref) => {
const id = useId() const id = useId();
return ( return (
// eslint-disable-next-line react/jsx-no-constructed-context-values
<FormItemContext.Provider value={{ id }}> <FormItemContext.Provider value={{ id }}>
<div <div
ref={ref} ref={ref}
@ -93,16 +71,38 @@ const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
{...props} {...props}
/> />
</FormItemContext.Provider> </FormItemContext.Provider>
) );
} },
) );
FormItem.displayName = 'FormItem' FormItem.displayName = 'FormItem';
const useFormField = () => {
const fieldContext = useContext(FormFieldContext);
const itemContext = useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>');
}
const { id } = itemContext;
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
};
};
const FormLabel = forwardRef< const FormLabel = forwardRef<
ComponentRef<typeof LabelPrimitive.Root>, ComponentRef<typeof LabelPrimitive.Root>,
ComponentPropsWithoutRef<typeof LabelPrimitive.Root> ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField() const { error, formItemId } = useFormField();
return ( return (
<Label <Label
@ -111,73 +111,74 @@ const FormLabel = forwardRef<
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> />
) );
}) });
FormLabel.displayName = 'FormLabel' FormLabel.displayName = 'FormLabel';
const FormControl = forwardRef< const FormControl = forwardRef<
ComponentRef<typeof Slot>, ComponentRef<typeof Slot>,
ComponentPropsWithoutRef<typeof Slot> ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => { >(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = const {
useFormField() error, formItemId, formDescriptionId, formMessageId,
} = useFormField();
return ( return (
<Slot <Slot
ref={ref} ref={ref}
id={formItemId}
aria-describedby={ aria-describedby={
!error !error
? `${formDescriptionId}` ? formDescriptionId
: `${formDescriptionId} ${formMessageId}` : `${formDescriptionId} ${formMessageId}`
} }
aria-invalid={!!error} aria-invalid={!!error}
id={formItemId}
{...props} {...props}
/> />
) );
}) });
FormControl.displayName = 'FormControl' FormControl.displayName = 'FormControl';
const FormDescription = forwardRef< const FormDescription = forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
HTMLAttributes<HTMLParagraphElement> HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => { >(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField() const { formDescriptionId } = useFormField();
return ( return (
<p <p
ref={ref} ref={ref}
id={formDescriptionId}
className={cn('text-sm text-muted-foreground', className)} className={cn('text-sm text-muted-foreground', className)}
id={formDescriptionId}
{...props} {...props}
/> />
) );
}) });
FormDescription.displayName = 'FormDescription' FormDescription.displayName = 'FormDescription';
const FormMessage = forwardRef< const FormMessage = forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
HTMLAttributes<HTMLParagraphElement> HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => { >(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField() const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children const body = error ? String(error?.message) : children;
if (!body) { if (!body) {
return null return null;
} }
return ( return (
<p <p
ref={ref} ref={ref}
id={formMessageId}
className={cn('text-sm font-medium text-destructive', className)} className={cn('text-sm font-medium text-destructive', className)}
id={formMessageId}
{...props} {...props}
> >
{body} {body}
</p> </p>
) );
}) });
FormMessage.displayName = 'FormMessage' FormMessage.displayName = 'FormMessage';
export { export {
Form, Form,
@ -187,5 +188,5 @@ export {
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage, FormMessage,
useFormField useFormField,
} };

View File

@ -1,22 +1,20 @@
import { cn } from '@/utils/twMerge'; import { type ComponentProps, forwardRef } from 'react';
import { type ComponentProps, forwardRef } from 'react'
import { cn } from '@/utils/twMerge';
const Input = forwardRef<HTMLInputElement, ComponentProps<'input'>>( const Input = forwardRef<HTMLInputElement, ComponentProps<'input'>>(
({ className, type, ...props }, ref) => { ({ className, type, ...props }, ref) => (
return ( <input
<input ref={ref}
type={type} className={cn(
className={cn( 'flex h-10 w-full rounded-md border border-border bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:border-primary focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full rounded-md border border-border bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:border-primary focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50', className,
className )}
)} type={type}
ref={ref} {...props}
{...props} />
/> ),
) );
} Input.displayName = 'Input';
)
Input.displayName = 'Input'
export { Input } export { Input };

View File

@ -1,15 +1,15 @@
'use client' 'use client';
import { cn } from '@/utils/twMerge'; import { OTPInput, OTPInputContext } from 'input-otp';
import { OTPInput, OTPInputContext } from 'input-otp' import { Dot } from 'lucide-react';
import { Dot } from 'lucide-react'
import { import {
type ComponentPropsWithoutRef, type ComponentPropsWithoutRef,
type ComponentRef, type ComponentRef,
forwardRef, forwardRef,
useContext useContext,
} from 'react' } from 'react';
import { cn } from '@/utils/twMerge';
const InputOTP = forwardRef< const InputOTP = forwardRef<
ComponentRef<typeof OTPInput>, ComponentRef<typeof OTPInput>,
@ -17,15 +17,15 @@ const InputOTP = forwardRef<
>(({ className, containerClassName, ...props }, ref) => ( >(({ className, containerClassName, ...props }, ref) => (
<OTPInput <OTPInput
ref={ref} ref={ref}
className={cn('disabled:cursor-not-allowed', className)}
containerClassName={cn( containerClassName={cn(
'flex items-center gap-2 has-disabled:opacity-50', 'flex items-center gap-2 has-disabled:opacity-50',
containerClassName containerClassName,
)} )}
className={cn('disabled:cursor-not-allowed', className)}
{...props} {...props}
/> />
)) ));
InputOTP.displayName = 'InputOTP' InputOTP.displayName = 'InputOTP';
const InputOTPGroup = forwardRef< const InputOTPGroup = forwardRef<
ComponentRef<'div'>, ComponentRef<'div'>,
@ -36,15 +36,15 @@ const InputOTPGroup = forwardRef<
className={cn('flex items-center gap-x-3', className)} className={cn('flex items-center gap-x-3', className)}
{...props} {...props}
/> />
)) ));
InputOTPGroup.displayName = 'InputOTPGroup' InputOTPGroup.displayName = 'InputOTPGroup';
const InputOTPSlot = forwardRef< const InputOTPSlot = forwardRef<
ComponentRef<'div'>, ComponentRef<'div'>,
ComponentPropsWithoutRef<'div'> & { index: number } ComponentPropsWithoutRef<'div'> & { index: number }
>(({ index, className, ...props }, ref) => { >(({ index, className, ...props }, ref) => {
const inputOTPContext = useContext(OTPInputContext) const inputOTPContext = useContext(OTPInputContext);
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index] const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
return ( return (
<div <div
@ -52,29 +52,34 @@ const InputOTPSlot = forwardRef<
className={cn( className={cn(
'relative flex h-10 w-14 items-center justify-center rounded-md border border-border text-sm transition-all', 'relative flex h-10 w-14 items-center justify-center rounded-md border border-border text-sm transition-all',
isActive && 'z-10 ring-2 ring-primary ring-offset-background', isActive && 'z-10 ring-2 ring-primary ring-offset-background',
className className,
)} )}
{...props} {...props}
> >
{char} {char}
{hasFakeCaret && (
<div className='pointer-events-none absolute inset-0 flex items-center justify-center'> {hasFakeCaret
<div className='animate-caret-blink h-4 w-px bg-foreground duration-1000' /> ? (
</div> <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
)} <div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
</div>
)
: null}
</div> </div>
) );
}) });
InputOTPSlot.displayName = 'InputOTPSlot' InputOTPSlot.displayName = 'InputOTPSlot';
const InputOTPSeparator = forwardRef< const InputOTPSeparator = forwardRef<
ComponentRef<'div'>, ComponentRef<'div'>,
ComponentPropsWithoutRef<'div'> ComponentPropsWithoutRef<'div'>
>(({ ...props }, ref) => ( >(({ ...props }, ref) => (
<div ref={ref} role='separator' {...props}> <div ref={ref} role="separator" {...props}>
<Dot /> <Dot />
</div> </div>
)) ));
InputOTPSeparator.displayName = 'InputOTPSeparator' InputOTPSeparator.displayName = 'InputOTPSeparator';
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } export {
InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,
};

View File

@ -1,30 +1,30 @@
'use client' 'use client';
import { cn } from '@/utils/twMerge' import * as LabelPrimitive from '@radix-ui/react-label';
import * as LabelPrimitive from '@radix-ui/react-label' import { type VariantProps, cva } from 'class-variance-authority';
import { type VariantProps, cva } from 'class-variance-authority'
import { import {
type ComponentPropsWithoutRef, type ComponentPropsWithoutRef,
type ComponentRef, type ComponentRef,
forwardRef forwardRef,
} from 'react' } from 'react';
import { cn } from '@/utils/twMerge';
const labelVariants = cva( const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
) );
const Label = forwardRef< const Label = forwardRef<
ComponentRef<typeof LabelPrimitive.Root>, ComponentRef<typeof LabelPrimitive.Root>,
ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
VariantProps<typeof labelVariants> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root
ref={ref} ref={ref}
className={cn(labelVariants(), className)} className={cn(labelVariants(), className)}
{...props} {...props}
/> />
)) ));
Label.displayName = LabelPrimitive.Root.displayName Label.displayName = LabelPrimitive.Root.displayName;
export { Label } export { Label };

View File

@ -1,25 +1,27 @@
"use client" 'use client';
import { useTheme } from "next-themes" import { useTheme } from 'next-themes';
import { Toaster as Sonner, ToasterProps } from "sonner" import { Toaster as Sonner } from 'sonner';
import type { ToasterProps } from 'sonner';
const Toaster = ({ ...props }: ToasterProps) => { const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme() const { theme = 'system' } = useTheme();
return ( return (
<Sonner <Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group" className="toaster group"
style={ style={
{ {
"--normal-bg": "var(--popover)", '--normal-bg': 'var(--popover)',
"--normal-text": "var(--popover-foreground)", '--normal-text': 'var(--popover-foreground)',
"--normal-border": "var(--border)", '--normal-border': 'var(--border)',
} as React.CSSProperties } as React.CSSProperties
} }
theme={theme as ToasterProps['theme']}
{...props} {...props}
/> />
) );
} };
export { Toaster } export { Toaster };

View File

@ -1,10 +1,10 @@
import messages from '../public/languages/ru.json'; import type messages from '../public/languages/ru.json';
const locales = ['ru', 'en'] as const; const locales = ['ru', 'en'] as const;
declare module 'next-intl' { declare module 'next-intl' {
interface AppConfig { type AppConfig = {
Locale: (typeof locales)[number]; Locale: (typeof locales)[number];
Messages: typeof messages; Messages: typeof messages;
} };
} }

View File

@ -2,10 +2,10 @@ import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
const httpLink = createHttpLink({ const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_SERVER_URL, uri: process.env.NEXT_PUBLIC_SERVER_URL,
credentials: 'include' credentials: 'include',
}) });
export const client = new ApolloClient({ export const client = new ApolloClient({
link: httpLink, link: httpLink,
cache: new InMemoryCache() cache: new InMemoryCache(),
}) });

View File

@ -1,5 +1,5 @@
export const COOKIE_NAME = 'language' export const COOKIE_NAME = 'language';
export const languages = ['ru', 'en'] as const export const languages = ['ru', 'en'] as const;
export const defaultLanguages: Language = 'ru'; export const defaultLanguages: Language = 'ru';
export type Language = (typeof languages)[number] export type Language = (typeof languages)[number];

View File

@ -1,14 +1,19 @@
'use server' 'use server';
import { cookies } from "next/headers"; import { cookies } from 'next/headers';
import { COOKIE_NAME, defaultLanguages, Language } from "./config";
import { COOKIE_NAME, defaultLanguages } from './config';
import type { Language } from './config';
export async function getCurrentLanguage() { export async function getCurrentLanguage() {
const cookieStore = await cookies(); const cookieStore = await cookies();
return <Language>cookieStore.get(COOKIE_NAME)?.value ?? defaultLanguages;
return cookieStore.get(COOKIE_NAME)?.value as Language ?? defaultLanguages;
} }
export async function setCurrentLanguage(lang: Language) { export async function setCurrentLanguage(lang: Language) {
const cookieStore = await cookies(); const cookieStore = await cookies();
return cookieStore.set(COOKIE_NAME, lang); return cookieStore.set(COOKIE_NAME, lang);
} }

View File

@ -1,5 +1,6 @@
import { getRequestConfig } from 'next-intl/server'; import { getRequestConfig } from 'next-intl/server';
import { getCurrentLanguage } from "./language"
import { getCurrentLanguage } from './language';
export default getRequestConfig(async () => { export default getRequestConfig(async () => {
const locale = await getCurrentLanguage(); const locale = await getCurrentLanguage();
@ -8,5 +9,5 @@ export default getRequestConfig(async () => {
return { return {
locale, locale,
messages, messages,
} };
}) });

View File

@ -1,11 +1,14 @@
'use client' 'use client';
import { ApolloProvider } from '@apollo/client';
import React from 'react';
import { client } from '@/libs/apollo-client'; import { client } from '@/libs/apollo-client';
import { ApolloProvider } from '@apollo/client';
import React, { PropsWithChildren } from 'react'; import type { PropsWithChildren } from 'react';
const ApolloClientProvider = (props: PropsWithChildren) => { const ApolloClientProvider = (props: PropsWithChildren) => {
const {children} = props; const { children } = props;
return ( return (
<ApolloProvider client={client}> <ApolloProvider client={client}>

View File

@ -1,10 +1,13 @@
"use client"; 'use client';
import * as React from "react"; import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { ThemeProvider as NextThemesProvider } from "next-themes"; import * as React from 'react';
export function ThemeProvider({children, export const ThemeProvider = ({
children,
...props ...props
}: React.ComponentProps<typeof NextThemesProvider>) { }: React.ComponentProps<typeof NextThemesProvider>) => (
return <NextThemesProvider {...props}>{children}</NextThemesProvider>; <NextThemesProvider {...props}>
} {children}
</NextThemesProvider>
);

View File

@ -1,25 +1,26 @@
'use client' 'use client';
import { useTheme } from 'next-themes' import { useTheme } from 'next-themes';
import type { ComponentProps } from 'react' import { Toaster as Sonner } from 'sonner';
import { Toaster as Sonner } from 'sonner'
type ToasterProps = ComponentProps<typeof Sonner> import type { ComponentProps } from 'react';
export function ToastProvider({ ...props }: ToasterProps) { type ToasterProps = ComponentProps<typeof Sonner>;
const { theme = 'system' } = useTheme()
export const ToastProvider = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme();
return ( return (
<Sonner <Sonner
className="toaster group"
theme={theme as ToasterProps['theme']} theme={theme as ToasterProps['theme']}
className='toaster group'
toastOptions={{ toastOptions={{
classNames: { classNames: {
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg', toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'text-muted-foreground' description: 'text-muted-foreground',
} },
}} }}
{...props} {...props}
/> />
) );
} };

View File

@ -1,12 +1,12 @@
import { z } from 'zod' import { z } from 'zod';
export const createAccountSchema = z.object({ export const createAccountSchema = z.object({
name: z name: z
.string() .string()
.min(1) .min(1)
.regex(/^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/), .regex(/^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/),
email: z.string().email({ pattern: z.regexes.html5Email }).min(3), email: z.email({ pattern: z.regexes.html5Email }).min(3),
password: z.string().min(8) password: z.string().min(8),
}) });
export type TypeCreateAccountSchema = z.infer<typeof createAccountSchema> export type TypeCreateAccountSchema = z.infer<typeof createAccountSchema>;

View File

@ -1,9 +1,9 @@
import { z } from 'zod' import { z } from 'zod';
export const loginSchema = z.object({ export const loginSchema = z.object({
login: z.string().min(1), login: z.string().min(1),
password: z.string().min(8), password: z.string().min(8),
pin: z.string().optional() pin: z.string().optional(),
}) });
export type TypeLoginSchema = z.infer<typeof loginSchema> export type TypeLoginSchema = z.infer<typeof loginSchema>;

View File

@ -1,12 +1,12 @@
import { z } from 'zod' import { z } from 'zod';
export const newPasswordSchema = z export const newPasswordSchema = z
.object({ .object({
password: z.string().min(8), password: z.string().min(8),
passwordRepeat: z.string().min(8) passwordRepeat: z.string().min(8),
})
.refine(data => data.password === data.passwordRepeat, {
path: ['passwordRepeat']
}) })
.refine((data) => data.password === data.passwordRepeat, {
path: ['passwordRepeat'],
});
export type TypeNewPasswordSchema = z.infer<typeof newPasswordSchema> export type TypeNewPasswordSchema = z.infer<typeof newPasswordSchema>;

View File

@ -1,7 +1,7 @@
import { z } from 'zod' import { z } from 'zod';
export const resetPasswordSchema = z.object({ export const resetPasswordSchema = z.object({
email: z.string().email() email: z.email(),
}) });
export type TypeResetPasswordSchema = z.infer<typeof resetPasswordSchema> export type TypeResetPasswordSchema = z.infer<typeof resetPasswordSchema>;

View File

@ -1,6 +1,6 @@
import { clsx, type ClassValue } from "clsx" import { clsx, type ClassValue } from 'clsx';
import { twMerge } from "tailwind-merge" import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }

View File

@ -451,7 +451,7 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.27.1" "@babel/helper-plugin-utils" "^7.27.1"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.26.10": "@babel/runtime@^7.0.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.26.10":
version "7.28.2" version "7.28.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473"
integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA== integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==
@ -1399,6 +1399,13 @@
dependencies: dependencies:
fast-glob "3.3.1" fast-glob "3.3.1"
"@next/eslint-plugin-next@15.4.5":
version "15.4.5"
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.5.tgz#fa46b04406c0b0aea22413b336f32595cd7613b6"
integrity sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw==
dependencies:
fast-glob "3.3.1"
"@next/swc-darwin-arm64@15.4.5": "@next/swc-darwin-arm64@15.4.5":
version "15.4.5" version "15.4.5"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.5.tgz#a716f1b8baf6dac0ac4cad9670350f637991f89e" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.5.tgz#a716f1b8baf6dac0ac4cad9670350f637991f89e"
@ -1516,6 +1523,18 @@
resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
"@stylistic/eslint-plugin@5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.2.0.tgz#fd664d2c81544cbe12c35d4af6d79b42814fc57f"
integrity sha512-RCEdbREv9EBiToUBQTlRhVYKG093I6ZnnQ990j08eJ6uRZh71DXkOnoxtTLfDQ6utVCVQzrhZFHZP0zfrfOIjA==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/types" "^8.37.0"
eslint-visitor-keys "^4.2.1"
espree "^10.4.0"
estraverse "^5.3.0"
picomatch "^4.0.3"
"@swc/helpers@0.5.15": "@swc/helpers@0.5.15":
version "0.5.15" version "0.5.15"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
@ -1712,6 +1731,21 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@typescript-eslint/eslint-plugin@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz#332392883f936137cd6252c8eb236d298e514e70"
integrity sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.37.0"
"@typescript-eslint/type-utils" "8.37.0"
"@typescript-eslint/utils" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
graphemer "^1.4.0"
ignore "^7.0.0"
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz#6e5220d16f2691ab6d983c1737dd5b36e17641b7" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz#6e5220d16f2691ab6d983c1737dd5b36e17641b7"
@ -1727,6 +1761,17 @@
natural-compare "^1.4.0" natural-compare "^1.4.0"
ts-api-utils "^2.1.0" ts-api-utils "^2.1.0"
"@typescript-eslint/parser@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.37.0.tgz#b87f6b61e25ad5cc5bbf8baf809b8da889c89804"
integrity sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==
dependencies:
"@typescript-eslint/scope-manager" "8.37.0"
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
debug "^4.3.4"
"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.38.0.tgz#6723a5ea881e1777956b1045cba30be5ea838293" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.38.0.tgz#6723a5ea881e1777956b1045cba30be5ea838293"
@ -1738,6 +1783,15 @@
"@typescript-eslint/visitor-keys" "8.38.0" "@typescript-eslint/visitor-keys" "8.38.0"
debug "^4.3.4" debug "^4.3.4"
"@typescript-eslint/project-service@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.37.0.tgz#0594352e32a4ac9258591b88af77b5653800cdfe"
integrity sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.37.0"
"@typescript-eslint/types" "^8.37.0"
debug "^4.3.4"
"@typescript-eslint/project-service@8.38.0": "@typescript-eslint/project-service@8.38.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.38.0.tgz#4900771f943163027fd7d2020a062892056b5e2f" resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.38.0.tgz#4900771f943163027fd7d2020a062892056b5e2f"
@ -1747,7 +1801,15 @@
"@typescript-eslint/types" "^8.38.0" "@typescript-eslint/types" "^8.38.0"
debug "^4.3.4" debug "^4.3.4"
"@typescript-eslint/scope-manager@8.38.0": "@typescript-eslint/scope-manager@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz#a31a3c80ca2ef4ed58de13742debb692e7d4c0a4"
integrity sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==
dependencies:
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
"@typescript-eslint/scope-manager@8.38.0", "@typescript-eslint/scope-manager@^8.15.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz#5a0efcb5c9cf6e4121b58f87972f567c69529226" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz#5a0efcb5c9cf6e4121b58f87972f567c69529226"
integrity sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ== integrity sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==
@ -1755,11 +1817,27 @@
"@typescript-eslint/types" "8.38.0" "@typescript-eslint/types" "8.38.0"
"@typescript-eslint/visitor-keys" "8.38.0" "@typescript-eslint/visitor-keys" "8.38.0"
"@typescript-eslint/tsconfig-utils@8.38.0", "@typescript-eslint/tsconfig-utils@^8.38.0": "@typescript-eslint/tsconfig-utils@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz#47a2760d265c6125f8e7864bc5c8537cad2bd053"
integrity sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==
"@typescript-eslint/tsconfig-utils@8.38.0", "@typescript-eslint/tsconfig-utils@^8.37.0", "@typescript-eslint/tsconfig-utils@^8.38.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz#6de4ce224a779601a8df667db56527255c42c4d0" resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz#6de4ce224a779601a8df667db56527255c42c4d0"
integrity sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ== integrity sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==
"@typescript-eslint/type-utils@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz#2a682e4c6ff5886712dad57e9787b5e417124507"
integrity sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==
dependencies:
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/utils" "8.37.0"
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/type-utils@8.38.0": "@typescript-eslint/type-utils@8.38.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz#a56cd84765fa6ec135fe252b5db61e304403a85b" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz#a56cd84765fa6ec135fe252b5db61e304403a85b"
@ -1771,11 +1849,32 @@
debug "^4.3.4" debug "^4.3.4"
ts-api-utils "^2.1.0" ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.38.0", "@typescript-eslint/types@^8.38.0": "@typescript-eslint/types@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.37.0.tgz#09517aa9625eb3c68941dde3ac8835740587b6ff"
integrity sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==
"@typescript-eslint/types@8.38.0", "@typescript-eslint/types@^8.37.0", "@typescript-eslint/types@^8.38.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.38.0.tgz#297351c994976b93c82ac0f0e206c8143aa82529" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.38.0.tgz#297351c994976b93c82ac0f0e206c8143aa82529"
integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw== integrity sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==
"@typescript-eslint/typescript-estree@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz#a07e4574d8e6e4355a558f61323730c987f5fcbc"
integrity sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==
dependencies:
"@typescript-eslint/project-service" "8.37.0"
"@typescript-eslint/tsconfig-utils" "8.37.0"
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/visitor-keys" "8.37.0"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
minimatch "^9.0.4"
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/typescript-estree@8.38.0": "@typescript-eslint/typescript-estree@8.38.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz#82262199eb6778bba28a319e25ad05b1158957df" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz#82262199eb6778bba28a319e25ad05b1158957df"
@ -1792,7 +1891,17 @@
semver "^7.6.0" semver "^7.6.0"
ts-api-utils "^2.1.0" ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.38.0": "@typescript-eslint/utils@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.37.0.tgz#189ea59b2709f5d898614611f091a776751ee335"
integrity sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/scope-manager" "8.37.0"
"@typescript-eslint/types" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/utils@8.38.0", "@typescript-eslint/utils@^8.0.0", "@typescript-eslint/utils@^8.15.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.38.0.tgz#5f10159899d30eb92ba70e642ca6f754bddbf15a" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.38.0.tgz#5f10159899d30eb92ba70e642ca6f754bddbf15a"
integrity sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg== integrity sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==
@ -1802,6 +1911,14 @@
"@typescript-eslint/types" "8.38.0" "@typescript-eslint/types" "8.38.0"
"@typescript-eslint/typescript-estree" "8.38.0" "@typescript-eslint/typescript-estree" "8.38.0"
"@typescript-eslint/visitor-keys@8.37.0":
version "8.37.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz#cdb6a6bd3e8d6dd69bd70c1bdda36e2d18737455"
integrity sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==
dependencies:
"@typescript-eslint/types" "8.37.0"
eslint-visitor-keys "^4.2.1"
"@typescript-eslint/visitor-keys@8.38.0": "@typescript-eslint/visitor-keys@8.38.0":
version "8.38.0" version "8.38.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz#a9765a527b082cb8fc60fd8a16e47c7ad5b60ea5" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz#a9765a527b082cb8fc60fd8a16e47c7ad5b60ea5"
@ -2594,7 +2711,7 @@ debounce@^1.2.0:
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
debug@4, debug@4.4.1, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0: debug@4, debug@4.4.1, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4.1:
version "4.4.1" version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
@ -2874,6 +2991,25 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-ksv741@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eslint-config-ksv741/-/eslint-config-ksv741-0.2.0.tgz#07adf285648823dad3b37821bd63eb598d6e4c9a"
integrity sha512-cGEY1VE79RuFrndqGEWeYow34W9ogi8JOuLOmL9QuPR66a07Evwu41dIlcBcXWcU493w/DXb9iQabJBmCMhYzQ==
dependencies:
"@eslint/eslintrc" "3.3.1"
"@stylistic/eslint-plugin" "5.2.0"
eslint-import-resolver-typescript "4.4.4"
eslint-plugin-import "2.32.0"
eslint-plugin-jest "29.0.1"
eslint-plugin-jest-dom "5.5.0"
eslint-plugin-jest-formatting "3.1.0"
eslint-plugin-jsx-a11y "6.10.2"
eslint-plugin-react "7.37.5"
eslint-plugin-react-hooks "5.2.0"
eslint-plugin-testing-library "7.6.0"
globals "16.3.0"
typescript-eslint "8.37.0"
eslint-config-next@15.4.4: eslint-config-next@15.4.4:
version "15.4.4" version "15.4.4"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.4.4.tgz#df20632c342b2b48e8450d0028b8fd15edb10a60" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-15.4.4.tgz#df20632c342b2b48e8450d0028b8fd15edb10a60"
@ -2890,6 +3026,14 @@ eslint-config-next@15.4.4:
eslint-plugin-react "^7.37.0" eslint-plugin-react "^7.37.0"
eslint-plugin-react-hooks "^5.0.0" eslint-plugin-react-hooks "^5.0.0"
eslint-import-context@^0.1.8:
version "0.1.9"
resolved "https://registry.yarnpkg.com/eslint-import-context/-/eslint-import-context-0.1.9.tgz#967b0b2f0a90ef4b689125e088f790f0b7756dbe"
integrity sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==
dependencies:
get-tsconfig "^4.10.1"
stable-hash-x "^0.2.0"
eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9: eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9:
version "0.3.9" version "0.3.9"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
@ -2899,6 +3043,19 @@ eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9:
is-core-module "^2.13.0" is-core-module "^2.13.0"
resolve "^1.22.4" resolve "^1.22.4"
eslint-import-resolver-typescript@4.4.4:
version "4.4.4"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz#3e83a9c25f4a053fe20e1b07b47e04e8519a8720"
integrity sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==
dependencies:
debug "^4.4.1"
eslint-import-context "^0.1.8"
get-tsconfig "^4.10.1"
is-bun-module "^2.0.0"
stable-hash-x "^0.2.0"
tinyglobby "^0.2.14"
unrs-resolver "^1.7.11"
eslint-import-resolver-typescript@^3.5.2: eslint-import-resolver-typescript@^3.5.2:
version "3.10.1" version "3.10.1"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz#23dac32efa86a88e2b8232eb244ac499ad636db2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz#23dac32efa86a88e2b8232eb244ac499ad636db2"
@ -2919,7 +3076,7 @@ eslint-module-utils@^2.12.1:
dependencies: dependencies:
debug "^3.2.7" debug "^3.2.7"
eslint-plugin-import@^2.31.0: eslint-plugin-import@2.32.0, eslint-plugin-import@^2.31.0:
version "2.32.0" version "2.32.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980"
integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==
@ -2944,7 +3101,27 @@ eslint-plugin-import@^2.31.0:
string.prototype.trimend "^1.0.9" string.prototype.trimend "^1.0.9"
tsconfig-paths "^3.15.0" tsconfig-paths "^3.15.0"
eslint-plugin-jsx-a11y@^6.10.0: eslint-plugin-jest-dom@5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-5.5.0.tgz#3ccdfe197eddb4108f390db583057a5dacccd4a0"
integrity sha512-CRlXfchTr7EgC3tDI7MGHY6QjdJU5Vv2RPaeeGtkXUHnKZf04kgzMPIJUXt4qKCvYWVVIEo9ut9Oq1vgXAykEA==
dependencies:
"@babel/runtime" "^7.16.3"
requireindex "^1.2.0"
eslint-plugin-jest-formatting@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest-formatting/-/eslint-plugin-jest-formatting-3.1.0.tgz#b26dd5a40f432b642dcc880021a771bb1c93dcd2"
integrity sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A==
eslint-plugin-jest@29.0.1:
version "29.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-29.0.1.tgz#0f72a81349409d20742208260c9a6cb9efed4df5"
integrity sha512-EE44T0OSMCeXhDrrdsbKAhprobKkPtJTbQz5yEktysNpHeDZTAL1SfDTNKmcFfJkY6yrQLtTKZALrD3j/Gpmiw==
dependencies:
"@typescript-eslint/utils" "^8.0.0"
eslint-plugin-jsx-a11y@6.10.2, eslint-plugin-jsx-a11y@^6.10.0:
version "6.10.2" version "6.10.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz#d2812bb23bf1ab4665f1718ea442e8372e638483" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz#d2812bb23bf1ab4665f1718ea442e8372e638483"
integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== integrity sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==
@ -2965,12 +3142,12 @@ eslint-plugin-jsx-a11y@^6.10.0:
safe-regex-test "^1.0.3" safe-regex-test "^1.0.3"
string.prototype.includes "^2.0.1" string.prototype.includes "^2.0.1"
eslint-plugin-react-hooks@^5.0.0: eslint-plugin-react-hooks@5.2.0, eslint-plugin-react-hooks@^5.0.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz#1be0080901e6ac31ce7971beed3d3ec0a423d9e3"
integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
eslint-plugin-react@^7.37.0: eslint-plugin-react@7.37.5, eslint-plugin-react@^7.37.0:
version "7.37.5" version "7.37.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065"
integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==
@ -2994,6 +3171,14 @@ eslint-plugin-react@^7.37.0:
string.prototype.matchall "^4.0.12" string.prototype.matchall "^4.0.12"
string.prototype.repeat "^1.0.0" string.prototype.repeat "^1.0.0"
eslint-plugin-testing-library@7.6.0:
version "7.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.6.0.tgz#ac6ab335c19d3da2a2456379c21a12a8e042783c"
integrity sha512-rxCz4VQFb45kDeFLnQcjGpeb72r4HmCh6v49d+DhrD2HVpnJuwK/GOnPjezWS7CytkNjQjpXcPopxLN++FlXxw==
dependencies:
"@typescript-eslint/scope-manager" "^8.15.0"
"@typescript-eslint/utils" "^8.15.0"
eslint-scope@^8.4.0: eslint-scope@^8.4.0:
version "8.4.0" version "8.4.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82"
@ -3311,7 +3496,7 @@ get-symbol-description@^1.1.0:
es-errors "^1.3.0" es-errors "^1.3.0"
get-intrinsic "^1.2.6" get-intrinsic "^1.2.6"
get-tsconfig@^4.10.0: get-tsconfig@^4.10.0, get-tsconfig@^4.10.1:
version "4.10.1" version "4.10.1"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.1.tgz#d34c1c01f47d65a606c37aa7a177bc3e56ab4b2e" resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.1.tgz#d34c1c01f47d65a606c37aa7a177bc3e56ab4b2e"
integrity sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ== integrity sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==
@ -3344,6 +3529,11 @@ glob@^7.1.1:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
globals@16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-16.3.0.tgz#66118e765ddaf9e2d880f7e17658543f93f1f667"
integrity sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==
globals@^14.0.0: globals@^14.0.0:
version "14.0.0" version "14.0.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
@ -4633,7 +4823,7 @@ picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.2: picomatch@^4.0.2, picomatch@^4.0.3:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
@ -4788,6 +4978,11 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
requireindex@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
resolve-from@5.0.0: resolve-from@5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
@ -5120,6 +5315,11 @@ sponge-case@^1.0.1:
dependencies: dependencies:
tslib "^2.0.3" tslib "^2.0.3"
stable-hash-x@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/stable-hash-x/-/stable-hash-x-0.2.0.tgz#dfd76bfa5d839a7470125c6a6b3c8b22061793e9"
integrity sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==
stable-hash@^0.0.5: stable-hash@^0.0.5:
version "0.0.5" version "0.0.5"
resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269" resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269"
@ -5316,7 +5516,7 @@ timeout-signal@^2.0.0:
resolved "https://registry.yarnpkg.com/timeout-signal/-/timeout-signal-2.0.0.tgz#23207ea448d50258bb0defe3beea4a467643abba" resolved "https://registry.yarnpkg.com/timeout-signal/-/timeout-signal-2.0.0.tgz#23207ea448d50258bb0defe3beea4a467643abba"
integrity sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA== integrity sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==
tinyglobby@^0.2.13: tinyglobby@^0.2.13, tinyglobby@^0.2.14:
version "0.2.14" version "0.2.14"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==
@ -5454,6 +5654,16 @@ typed-array-length@^1.0.7:
possible-typed-array-names "^1.0.0" possible-typed-array-names "^1.0.0"
reflect.getprototypeof "^1.0.6" reflect.getprototypeof "^1.0.6"
typescript-eslint@8.37.0:
version "8.37.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.37.0.tgz#2235ddfa40cdbdadb1afb05f8bda688a2294b4c2"
integrity sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==
dependencies:
"@typescript-eslint/eslint-plugin" "8.37.0"
"@typescript-eslint/parser" "8.37.0"
"@typescript-eslint/typescript-estree" "8.37.0"
"@typescript-eslint/utils" "8.37.0"
typescript@5.8.3: typescript@5.8.3:
version "5.8.3" version "5.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
@ -5496,7 +5706,7 @@ unixify@^1.0.0:
dependencies: dependencies:
normalize-path "^2.1.1" normalize-path "^2.1.1"
unrs-resolver@^1.6.2: unrs-resolver@^1.6.2, unrs-resolver@^1.7.11:
version "1.11.1" version "1.11.1"
resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9"
integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==