From a6cef0298d9383cd1afdd5befcfdc495abf6f2e4 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 3 Aug 2025 09:40:49 +0300 Subject: [PATCH] [frontend]: add eslint --- frontend/eslint.config.mjs | 126 +++++++++- frontend/package.json | 2 + frontend/src/app/account/create/page.tsx | 18 +- frontend/src/app/account/login/page.tsx | 18 +- .../src/app/account/recovery/[token]/page.tsx | 18 +- frontend/src/app/account/recovery/page.tsx | 18 +- frontend/src/app/account/verify/page.tsx | 19 +- frontend/src/app/layout.tsx | 44 ++-- frontend/src/app/page.tsx | 2 +- .../components/features/auth/AuthWrapper.tsx | 60 +++-- .../features/auth/forms/CreateAccountForm.tsx | 230 +++++++++-------- .../features/auth/forms/LoginAccountForm.tsx | 209 ++++++++------- .../features/auth/forms/NewPasswordForm.tsx | 90 ++++--- .../features/auth/forms/ResetPasswordForm.tsx | 172 +++++++------ .../auth/forms/VerifyAccoiuntForm.tsx | 53 ++-- frontend/src/components/images/LogoImage.tsx | 81 +++--- frontend/src/components/ui/common/Alert.tsx | 37 +-- frontend/src/components/ui/common/Button.tsx | 49 ++-- frontend/src/components/ui/common/Card.tsx | 46 ++-- frontend/src/components/ui/common/Form.tsx | 163 ++++++------ frontend/src/components/ui/common/Input.tsx | 34 ++- .../src/components/ui/common/InputOTP.tsx | 59 +++-- frontend/src/components/ui/common/Label.tsx | 26 +- frontend/src/components/ui/common/Toast.tsx | 24 +- frontend/src/global.d.ts | 6 +- frontend/src/libs/apollo-client.ts | 8 +- frontend/src/libs/i18n/config.ts | 6 +- frontend/src/libs/i18n/language.ts | 13 +- frontend/src/libs/i18n/request.ts | 7 +- .../src/providers/ApolloClientProvider.tsx | 11 +- frontend/src/providers/ThemeProvider.tsx | 17 +- frontend/src/providers/ToastProvider.tsx | 25 +- .../src/schemas/auth/create-account.schema.ts | 10 +- frontend/src/schemas/auth/login.schema.ts | 8 +- .../src/schemas/auth/new-password.schema.ts | 12 +- .../src/schemas/auth/reset-password.schema.ts | 8 +- frontend/src/utils/twMerge.ts | 6 +- frontend/yarn.lock | 238 ++++++++++++++++-- 38 files changed, 1202 insertions(+), 771 deletions(-) diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs index c59fee2..cd97840 100644 --- a/frontend/eslint.config.mjs +++ b/frontend/eslint.config.mjs @@ -1,19 +1,121 @@ -import { dirname } from "path"; -import { fileURLToPath } from "url"; -import { FlatCompat } from "@eslint/eslintrc"; +import config from 'eslint-config-ksv741'; +import nextPlugin from '@next/eslint-plugin-next' -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +const {flatConfig} = nextPlugin; -const compat = new FlatCompat({ - baseDirectory: __dirname, -}); +export default [ + ...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 = [ - ...compat.extends("next/core-web-vitals", "next/typescript"), + 'react/destructuring-assignment': ['off', 'never', { ignoreClassFields: true, destructureInSignature: 'always' }], + "@typescript-eslint/no-misused-promises": ["error", { + "checksVoidReturn": false + } + ] + }, + }, { ignores: ['src/graphql/generated'] } ]; - -export default eslintConfig; diff --git a/frontend/package.json b/frontend/package.json index b71cc4a..ee97116 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,11 +35,13 @@ }, "devDependencies": { "@eslint/eslintrc": "3.3.1", + "@next/eslint-plugin-next": "15.4.5", "@tailwindcss/postcss": "4.1.11", "@types/node": "22.17.0", "@types/react": "19.1.9", "@types/react-dom": "19.1.7", "eslint": "9.32.0", + "eslint-config-ksv741": "0.2.0", "eslint-config-next": "15.4.4", "tailwindcss": "4.1.11", "tw-animate-css": "1.3.6", diff --git a/frontend/src/app/account/create/page.tsx b/frontend/src/app/account/create/page.tsx index c69a3d3..0029f55 100644 --- a/frontend/src/app/account/create/page.tsx +++ b/frontend/src/app/account/create/page.tsx @@ -1,20 +1,20 @@ -import CreateAccountForm from '@/components/features/auth/forms/CreateAccountForm'; -import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import React from 'react'; +import CreateAccountForm from '@/components/features/auth/forms/CreateAccountForm'; + +import type { Metadata } from 'next'; + export async function generateMetadata(): Promise { const t = await getTranslations('auth.register'); return { - title: t('heading') - } + title: t('heading'), + }; } -const CreateAccountPage = () => { - return ( - - ); -}; +const CreateAccountPage = () => ( + +); export default CreateAccountPage; diff --git a/frontend/src/app/account/login/page.tsx b/frontend/src/app/account/login/page.tsx index b0e3a4c..ec30609 100644 --- a/frontend/src/app/account/login/page.tsx +++ b/frontend/src/app/account/login/page.tsx @@ -1,20 +1,20 @@ -import LoginAccountForm from '@/components/features/auth/forms/LoginAccountForm'; -import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import React from 'react'; +import LoginAccountForm from '@/components/features/auth/forms/LoginAccountForm'; + +import type { Metadata } from 'next'; + export async function generateMetadata(): Promise { const t = await getTranslations('auth.login'); return { - title: t('heading') - } + title: t('heading'), + }; } -const LoginAccountPage = () => { - return ( - - ); -}; +const LoginAccountPage = () => ( + +); export default LoginAccountPage; diff --git a/frontend/src/app/account/recovery/[token]/page.tsx b/frontend/src/app/account/recovery/[token]/page.tsx index 810f5f3..541f88d 100644 --- a/frontend/src/app/account/recovery/[token]/page.tsx +++ b/frontend/src/app/account/recovery/[token]/page.tsx @@ -1,20 +1,20 @@ -import { NewPasswordForm } from '@/components/features/auth/forms/NewPasswordForm'; -import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import React from 'react'; +import { NewPasswordForm } from '@/components/features/auth/forms/NewPasswordForm'; + +import type { Metadata } from 'next'; + export async function generateMetadata(): Promise { const t = await getTranslations('auth.newPassword'); return { - title: t('heading') - } + title: t('heading'), + }; } -const NewPasswordPage = () => { - return ( - - ); -}; +const NewPasswordPage = () => ( + +); export default NewPasswordPage; diff --git a/frontend/src/app/account/recovery/page.tsx b/frontend/src/app/account/recovery/page.tsx index 58fa8b0..bb442a5 100644 --- a/frontend/src/app/account/recovery/page.tsx +++ b/frontend/src/app/account/recovery/page.tsx @@ -1,20 +1,20 @@ -import { ResetPasswordForm } from '@/components/features/auth/forms/ResetPasswordForm'; -import { Metadata } from 'next'; import { getTranslations } from 'next-intl/server'; import React from 'react'; +import { ResetPasswordForm } from '@/components/features/auth/forms/ResetPasswordForm'; + +import type { Metadata } from 'next'; + export async function generateMetadata(): Promise { const t = await getTranslations('auth.resetPassword'); return { - title: t('heading') - } + title: t('heading'), + }; } -const ResetPasswordPage = () => { - return ( - - ); -}; +const ResetPasswordPage = () => ( + +); export default ResetPasswordPage; diff --git a/frontend/src/app/account/verify/page.tsx b/frontend/src/app/account/verify/page.tsx index 54ab81e..afa61f8 100644 --- a/frontend/src/app/account/verify/page.tsx +++ b/frontend/src/app/account/verify/page.tsx @@ -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 { getTranslations } from 'next-intl/server'; import React from 'react'; +import { VerifyAccountForm } from '@/components/features/auth/forms/VerifyAccoiuntForm'; + +import type { Metadata } from 'next'; export async function generateMetadata(): Promise { const t = await getTranslations('auth.verify'); return { - title: t('heading') - } + title: t('heading'), + }; } type VerifyAccountPageProps = { - searchParams: Promise<{token: string}> -} + searchParams: Promise<{ token: string }>; +}; const VerifyAccountPage = async (props: VerifyAccountPageProps) => { - const {token} = await props.searchParams; + const { token } = await props.searchParams; if (!token) { - return redirect('/account/create') + return redirect('/account/create'); } return ( diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 2e6a234..b676ca0 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -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 { ThemeProvider } from '@/providers/ThemeProvider'; import { ToastProvider } from '@/providers/ToastProvider'; -import type { Metadata } from "next"; -import { NextIntlClientProvider } from 'next-intl'; -import { getLocale, getMessages } from 'next-intl/server'; -import { Geist } from "next/font/google"; -import "../styles/globals.css"; + +import '../styles/globals.css'; +import type { Metadata } from 'next'; +import type { ReactNode } from 'react'; const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], + variable: '--font-geist-sans', + subsets: ['latin'], }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: 'Create Next App', + description: 'Generated by create next app', }; -export default async function RootLayout({ +const RootLayout = async ({ children, }: Readonly<{ - children: React.ReactNode; -}>) { - const locale = await getLocale() - const messages = await getMessages() + children: ReactNode; +}>) => { + const locale = await getLocale(); + const messages = await getMessages(); return ( - + - + + {children} @@ -44,4 +48,6 @@ export default async function RootLayout({ ); -} +}; + +export default RootLayout; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 4ff237b..e31bc52 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,4 +1,4 @@ -'use client' +'use client'; import { redirect } from 'next/navigation'; diff --git a/frontend/src/components/features/auth/AuthWrapper.tsx b/frontend/src/components/features/auth/AuthWrapper.tsx index 7921ff6..6d92c7a 100644 --- a/frontend/src/components/features/auth/AuthWrapper.tsx +++ b/frontend/src/components/features/auth/AuthWrapper.tsx @@ -1,34 +1,50 @@ +import Link from 'next/link'; +import React from 'react'; + import { LogoImage } from '@/components/images/LogoImage'; import { Button } from '@/components/ui/common/Button'; -import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/common/Card'; -import Link from 'next/link'; -import React, { FC, ReactNode } from 'react'; +import { + Card, CardContent, CardFooter, CardHeader, CardTitle, +} from '@/components/ui/common/Card'; + +import type { FC, ReactNode } from 'react'; type AuthWrapperProps = { - heading: string - backButtonLabel?: string - backButtonHref?: string - children: ReactNode -} + heading: string; + backButtonLabel?: string; + backButtonHref?: string; + children: ReactNode; +}; const AuthWrapper: FC = (props) => { - const { children, backButtonHref, backButtonLabel, heading } = props; + const { + children, backButtonHref, backButtonLabel, heading, + } = props; return ( -
- - +
+ + - {heading} + + + {heading} + - {children} - - {backButtonLabel && backButtonHref && ( - - - - )} + + + {children} + + + + {backButtonLabel && backButtonHref + ? ( + + + + ) + : null}
diff --git a/frontend/src/components/features/auth/forms/CreateAccountForm.tsx b/frontend/src/components/features/auth/forms/CreateAccountForm.tsx index 2521d5f..79b5cdd 100644 --- a/frontend/src/components/features/auth/forms/CreateAccountForm.tsx +++ b/frontend/src/components/features/auth/forms/CreateAccountForm.tsx @@ -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 { CircleCheck } from 'lucide-react'; import { useTranslations } from 'next-intl'; +import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; 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 [isSuccess, setIsSuccess] = useState(false) - const t = useTranslations('auth.register') + const [isSuccess, setIsSuccess] = useState(false); + const t = useTranslations('auth.register'); const form = useForm({ resolver: zodResolver(createAccountSchema), defaultValues: { name: '', email: '', - password: '' - } - }) - const { isValid } = form.formState + password: '', + }, + }); + const { isValid } = form.formState; const [create, { loading: isLoadingCreate }] = useCreateUserMutation({ onCompleted() { - setIsSuccess(true) + setIsSuccess(true); }, onError() { - toast.error(t('errorMessage')) - } - }) + toast.error(t('errorMessage')); + }, + }); function onSubmit(data: TypeCreateAccountSchema) { - create({ variables: { data } }) + void create({ variables: { data } }); } return ( - {isSuccess ? ( - - - {t('successAlertTitle')} - - {t('successAlertDescription')} - - - ) : ( -
- - ( - - {t('usernameLabel')} - - - - - {t('usernameDescription')} - - - )} - /> - ( - - {t('emailLabel')} - - - - - {t('emailDescription')} - - - )} - /> - ( - - {t('passwordLabel')} - - - - - {t('passwordDescription')} - - - )} - /> - - - - )} + ( + + + {t('usernameLabel')} + + + + + + + + {t('usernameDescription')} + + + )} + /> + + ( + + + {t('emailLabel')} + + + + + + + + {t('emailDescription')} + + + )} + /> + + ( + + + {t('passwordLabel')} + + + + + + + + {t('passwordDescription')} + + + )} + /> + + + + + )}
); }; diff --git a/frontend/src/components/features/auth/forms/LoginAccountForm.tsx b/frontend/src/components/features/auth/forms/LoginAccountForm.tsx index ff8400f..3e1ef16 100644 --- a/frontend/src/components/features/auth/forms/LoginAccountForm.tsx +++ b/frontend/src/components/features/auth/forms/LoginAccountForm.tsx @@ -1,24 +1,29 @@ -'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'; +'use client'; +import { zodResolver } from '@hookform/resolvers/zod'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; import React, { useState } from 'react'; import { useForm } from 'react-hook-form'; 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 { 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 t = useTranslations('auth.login') - const [isShowTwoFactor, setIsShowTwoFactor] = useState(false) + const t = useTranslations('auth.login'); + const [isShowTwoFactor, setIsShowTwoFactor] = useState(false); const router = useRouter(); const form = useForm({ @@ -26,119 +31,139 @@ const LoginAccountForm = () => { defaultValues: { login: '', password: '', - } - }) - const { isValid } = form.formState + }, + }); + const { isValid } = form.formState; - const [login, {loading: isLoadingLogin}] = useLoginUserMutation({ + const [login, { loading: isLoadingLogin }] = useLoginUserMutation({ onCompleted(data) { if (data.loginUser.message) { - setIsShowTwoFactor(true) + setIsShowTwoFactor(true); } else { - toast.success(t('successMessage')) - router.push('/dashboard/settings') + toast.success(t('successMessage')); + router.push('/dashboard/settings'); } }, onError: () => { - toast.error(t('errorMessage')) - } - }) + toast.error(t('errorMessage')); + }, + }); function onSubmit(data: TypeLoginSchema) { - void login({variables: {data}}) + void login({ variables: { data } }); } return (
- {isShowTwoFactor ? ( - ( - - {t('pinLabel')} - - - - - - - - - - - - - - {t('pinDescription')} - - - )} - /> - ) : ( - <> + {isShowTwoFactor + ? ( ( - {t('loginLabel')} + + {t('pinLabel')} + + - + + + + + + + + + + + + + + + + - {t('loginDescription')} + {t('pinDescription')} )} /> - ( - -
+ ) + : ( + <> + ( + - {t('passwordLabel')} + {t('loginLabel')} - - {t('forgotPassword')} - -
- - - - - {t('passwordDescription')} - -
- )} - /> - - )} + + + + + + + {t('loginDescription')} + + + )} + /> + + ( + +
+ + {t('passwordLabel')} + + + + {t('forgotPassword')} + +
+ + + + + + + {t('passwordDescription')} + +
+ )} + /> + + )} + - - - )} + ( + + + {t('emailLabel')} + + + + + + + + {t('emailDescription')} + + + )} + /> + + + + + )}
- ) -} + ); +}; diff --git a/frontend/src/components/features/auth/forms/VerifyAccoiuntForm.tsx b/frontend/src/components/features/auth/forms/VerifyAccoiuntForm.tsx index 737b06d..a10cfe4 100644 --- a/frontend/src/components/features/auth/forms/VerifyAccoiuntForm.tsx +++ b/frontend/src/components/features/auth/forms/VerifyAccoiuntForm.tsx @@ -1,46 +1,47 @@ -'use client' +'use client'; -import { Loader } from 'lucide-react' -import { useTranslations } from 'next-intl' -import { useRouter } from 'next/navigation' -import { useEffect } from 'react' -import { toast } from 'sonner' +import { Loader } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; +import { useEffect } from 'react'; +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 = { - token: string -} -export function VerifyAccountForm(props: VerifyAccountFormProps) { + token: string; +}; +export const VerifyAccountForm = (props: VerifyAccountFormProps) => { const { token } = props; - const t = useTranslations('auth.verify') + const t = useTranslations('auth.verify'); - const router = useRouter() + const router = useRouter(); const [verify] = useVerifyAccountMutation({ onCompleted() { - toast.success(t('successMessage')) - router.push('/dashboard/settings') + toast.success(t('successMessage')); + router.push('/dashboard/settings'); }, onError() { - toast.error(t('errorMessage')) - } - }) + toast.error(t('errorMessage')); + }, + }); useEffect(() => { void verify({ variables: { - data: { token } - } - }) - }, [token, verify]) + data: { token }, + }, + }); + }, [token, verify]); return ( -
- +
+
- ) -} + ); +}; diff --git a/frontend/src/components/images/LogoImage.tsx b/frontend/src/components/images/LogoImage.tsx index fc63fca..5b4c396 100644 --- a/frontend/src/components/images/LogoImage.tsx +++ b/frontend/src/components/images/LogoImage.tsx @@ -1,43 +1,44 @@ -export function LogoImage() { - return ( - - Logo +export const LogoImage = () => ( + + Logo + + + + - - - - - - - + + + + + + - - ) -} + + +); diff --git a/frontend/src/components/ui/common/Alert.tsx b/frontend/src/components/ui/common/Alert.tsx index de96dc3..3c2b880 100644 --- a/frontend/src/components/ui/common/Alert.tsx +++ b/frontend/src/components/ui/common/Alert.tsx @@ -1,7 +1,7 @@ -import { cn } from '@/utils/twMerge' -import { type VariantProps, cva } from 'class-variance-authority' -import { type HTMLAttributes, forwardRef } from 'react' +import { type VariantProps, cva } from 'class-variance-authority'; +import { type HTMLAttributes, forwardRef } from 'react'; +import { cn } from '@/utils/twMerge'; 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', @@ -10,14 +10,14 @@ const alertVariants = cva( variant: { default: 'bg-background text-foreground', destructive: - 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive' - } + 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', + }, }, defaultVariants: { - variant: 'default' - } - } -) + variant: 'default', + }, + }, +); const Alert = forwardRef< HTMLDivElement, @@ -25,24 +25,25 @@ const Alert = forwardRef< >(({ className, variant, ...props }, ref) => (
-)) -Alert.displayName = 'Alert' +)); +Alert.displayName = 'Alert'; const AlertTitle = forwardRef< HTMLParagraphElement, HTMLAttributes >(({ className, ...props }, ref) => ( + // eslint-disable-next-line jsx-a11y/heading-has-content
-)) -AlertTitle.displayName = 'AlertTitle' +)); +AlertTitle.displayName = 'AlertTitle'; const AlertDescription = forwardRef< HTMLParagraphElement, @@ -52,11 +53,11 @@ const AlertDescription = forwardRef< ref={ref} className={cn( 'mt-2 text-sm text-muted-foreground [&_p]:leading-relaxed', - className + className, )} {...props} /> -)) -AlertDescription.displayName = 'AlertDescription' +)); +AlertDescription.displayName = 'AlertDescription'; -export { Alert, AlertDescription, AlertTitle } +export { Alert, AlertDescription, AlertTitle }; diff --git a/frontend/src/components/ui/common/Button.tsx b/frontend/src/components/ui/common/Button.tsx index d4214ed..94e823f 100644 --- a/frontend/src/components/ui/common/Button.tsx +++ b/frontend/src/components/ui/common/Button.tsx @@ -1,8 +1,8 @@ -import { cn } from '@/utils/twMerge' -import { Slot } from '@radix-ui/react-slot' -import { type VariantProps, cva } from 'class-variance-authority' -import { type ButtonHTMLAttributes, forwardRef } from 'react' +import { Slot } from '@radix-ui/react-slot'; +import { type VariantProps, cva } from 'class-variance-authority'; +import { type ButtonHTMLAttributes, forwardRef } from 'react'; +import { cn } from '@/utils/twMerge'; 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', @@ -12,39 +12,40 @@ const buttonVariants = cva( default: 'bg-primary text-primary-foreground', outline: 'border border-border bg-background', 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: { default: 'h-10 px-5 py-2 rounded-full', icon: 'size-8 rounded-full', - lgIcon: 'size-10 rounded-full' - } + lgIcon: 'size-10 rounded-full', + }, }, defaultVariants: { variant: 'default', - size: 'default' - } - } -) + size: 'default', + }, + }, +); -export interface ButtonProps - extends ButtonHTMLAttributes, - VariantProps { - asChild?: boolean -} +export type ButtonProps = ButtonHTMLAttributes & VariantProps & { + asChild?: boolean; +}; const Button = forwardRef( - ({ 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 ( - ) - } -) -Button.displayName = 'Button' + ); + }, +); +Button.displayName = 'Button'; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/frontend/src/components/ui/common/Card.tsx b/frontend/src/components/ui/common/Card.tsx index a9a3022..36160af 100644 --- a/frontend/src/components/ui/common/Card.tsx +++ b/frontend/src/components/ui/common/Card.tsx @@ -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>( ({ className, ...props }, ref) => ( @@ -8,13 +8,13 @@ const Card = forwardRef>( ref={ref} className={cn( 'bg-card text-card-foreground border-border rounded-lg border shadow-sm', - className + className, )} {...props} /> - ) -) -Card.displayName = 'Card' + ), +); +Card.displayName = 'Card'; const CardHeader = forwardRef>( ({ className, ...props }, ref) => ( @@ -23,9 +23,9 @@ const CardHeader = forwardRef>( className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} /> - ) -) -CardHeader.displayName = 'CardHeader' + ), +); +CardHeader.displayName = 'CardHeader'; const CardTitle = forwardRef>( ({ className, ...props }, ref) => ( @@ -33,13 +33,13 @@ const CardTitle = forwardRef>( ref={ref} className={cn( 'text-2xl font-semibold leading-none tracking-wide', - className + className, )} {...props} /> - ) -) -CardTitle.displayName = 'CardTitle' + ), +); +CardTitle.displayName = 'CardTitle'; const CardDescription = forwardRef< HTMLDivElement, @@ -50,15 +50,15 @@ const CardDescription = forwardRef< className={cn('text-muted-foreground text-sm', className)} {...props} /> -)) -CardDescription.displayName = 'CardDescription' +)); +CardDescription.displayName = 'CardDescription'; const CardContent = forwardRef>( ({ className, ...props }, ref) => (
- ) -) -CardContent.displayName = 'CardContent' + ), +); +CardContent.displayName = 'CardContent'; const CardFooter = forwardRef>( ({ className, ...props }, ref) => ( @@ -67,8 +67,10 @@ const CardFooter = forwardRef>( className={cn('flex items-center p-6 pt-0', className)} {...props} /> - ) -) -CardFooter.displayName = 'CardFooter' + ), +); +CardFooter.displayName = 'CardFooter'; -export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } +export { + Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, +}; diff --git a/frontend/src/components/ui/common/Form.tsx b/frontend/src/components/ui/common/Form.tsx index a7d7a99..1b52fc0 100644 --- a/frontend/src/components/ui/common/Form.tsx +++ b/frontend/src/components/ui/common/Form.tsx @@ -1,8 +1,6 @@ -'use client' +'use client'; -import { cn } from '@/utils/twMerge'; -import * as LabelPrimitive from '@radix-ui/react-label' -import { Slot } from '@radix-ui/react-slot' +import { Slot } from '@radix-ui/react-slot'; import { type ComponentPropsWithoutRef, type ComponentRef, @@ -10,82 +8,62 @@ import { createContext, forwardRef, useContext, - useId -} from 'react' + useId, +} from 'react'; import { Controller, type ControllerProps, type FieldPath, type FieldValues, FormProvider, - useFormContext -} from 'react-hook-form' + useFormContext, +} 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< TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath + TName extends FieldPath = FieldPath, > = { - name: TName -} + name: TName; +}; const FormFieldContext = createContext( - {} as FormFieldContextValue -) + {} as FormFieldContextValue, +); const FormField = < TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath + TName extends FieldPath = FieldPath, >({ - ...props - }: ControllerProps) => { - return ( - - - - ) -} - -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 ') - } - - const { id } = itemContext - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState - } -} + ...props +}: ControllerProps) => ( + // eslint-disable-next-line react/jsx-no-constructed-context-values + + + +); type FormItemContextValue = { - id: string -} + id: string; +}; const FormItemContext = createContext( - {} as FormItemContextValue -) + {} as FormItemContextValue, +); const FormItem = forwardRef>( ({ className, ...props }, ref) => { - const id = useId() + const id = useId(); return ( + // eslint-disable-next-line react/jsx-no-constructed-context-values
>( {...props} /> - ) - } -) -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 '); + } + + 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< ComponentRef, ComponentPropsWithoutRef >(({ className, ...props }, ref) => { - const { error, formItemId } = useFormField() + const { error, formItemId } = useFormField(); return (