diff --git a/app/(auth)/oauth/page.tsx b/app/(auth)/oauth/page.tsx deleted file mode 100644 index 3fad911..0000000 --- a/app/(auth)/oauth/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { OAuthPage as default } from 'pages/auth/oauth'; diff --git a/app/(auth)/oauth/route.ts b/app/(auth)/oauth/route.ts new file mode 100644 index 0000000..9e3a7a1 --- /dev/null +++ b/app/(auth)/oauth/route.ts @@ -0,0 +1,85 @@ +import { type TAuth } from 'entities/auth'; +import { StartOauthParams } from 'features/auth/oauth-login'; +import { NextRequest, NextResponse } from 'next/server'; +import { env, routes } from 'shared/config'; + +type OAuthParams = + | { + success: 'false' | 'true'; + token?: string; + provider: TAuth.OAuthProvider; + } + | StartOauthParams; + +const ERROR_MESSAGE = 'Не удалось выполнить авторизацию'; + +export async function GET(request: NextRequest) { + const params: Partial = Object.fromEntries(request.nextUrl.searchParams); + + //start oauth + if ( + 'provider' in params && + 'startOAuth' in params && + params.startOAuth === 'true' && + params.provider + ) { + const { provider } = params; + const redirectUrl = `${env.NEXT_PUBLIC_API_BASE_URL}/oauth/${provider}`; + + return NextResponse.redirect(redirectUrl); + } + + //exchange token + if ('token' in params && params.token) { + try { + const { token, success, provider } = params; + + if (success === 'false') { + throw new Error(ERROR_MESSAGE); + } + + if (!provider) { + throw new Error('OAuth provider не найден в query параметрах'); + } + + const response = await fetch(`${env.NEXT_PUBLIC_API_BASE_URL}/oauth/exchange`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token, provider } satisfies TAuth.ExchangeTokenBody), + }); + + const data = (await response.json()) as TAuth.ExchangeTokenResponse; + + if (!response.ok || !data.success) { + throw new Error( + data.message || `OAuth exchange завершился с ошибкой (status ${response.status})` + ); + } + + const successUrl = new URL(routes.user.profile(), request.url); + + successUrl.searchParams.set('success', 'true'); + successUrl.searchParams.set('message', 'Операция выполнена успешно'); + + const res = NextResponse.redirect(successUrl); + const cookies = response.headers.getSetCookie() ?? []; + + cookies.forEach((cookie) => { + res.headers.append('Set-Cookie', cookie); + }); + + return res; + } catch (error) { + console.error(error instanceof Error ? error.message : ERROR_MESSAGE); + } + } + + const errorUrl = new URL(routes.auth.signin(), request.url); + + errorUrl.searchParams.set('success', 'false'); + errorUrl.searchParams.set('message', ERROR_MESSAGE); + + return NextResponse.redirect(errorUrl); +} diff --git a/src/entities/auth/api/http.ts b/src/entities/auth/api/http.ts index 959f5a1..170d346 100644 --- a/src/entities/auth/api/http.ts +++ b/src/entities/auth/api/http.ts @@ -88,7 +88,7 @@ export class AuthHttp { } static oAuthProviders(signal: AbortSignal) { return api({ - url: '/auth/oauth/providers', + url: '/oauth/providers', method: 'GET', contracts: { response: SAuth.OAuthProvidersResponse, @@ -98,7 +98,7 @@ export class AuthHttp { } static connectedOAuthProviders(signal: AbortSignal) { return api({ - url: '/auth/oauth/providers/connected', + url: '/oauth/providers/connected', method: 'GET', contracts: { response: SAuth.ConnectedOAuthProvidersResponse, @@ -108,7 +108,7 @@ export class AuthHttp { } static connectOAuthProvder(provider: TAuth.OAuthProvider) { return api({ - url: `/auth/oauth/${provider}/connect`, + url: `/oauth/${provider}/connect`, method: 'POST', contracts: { response: SAuth.ConnectOAuthProviderResponse, @@ -117,7 +117,7 @@ export class AuthHttp { } static removeOAuthProvder(provider: TAuth.OAuthProvider) { return api({ - url: `/auth/oauth/${provider}/connect`, + url: `/oauth/${provider}/connect`, method: 'DELETE', contracts: { response: SAuth.RemoveOAuthProviderResponse, @@ -126,7 +126,7 @@ export class AuthHttp { } static resendCode(data: TAuth.ResendCodeBody): Promise { return api({ - url: '/auth/resend', + url: '/oauth/resend', method: 'POST', data: data, contracts: { @@ -135,4 +135,11 @@ export class AuthHttp { }, }); } + static exchangeToken(data: TAuth.ExchangeTokenBody): Promise { + return api({ + url: '/oauth/exchange', + method: 'POST', + data: data, + }); + } } diff --git a/src/entities/auth/config/oauth-providers.ts b/src/entities/auth/config/oauth-providers.ts index ea62141..c5da6a9 100644 --- a/src/entities/auth/config/oauth-providers.ts +++ b/src/entities/auth/config/oauth-providers.ts @@ -1,26 +1,26 @@ -import { type TAuth } from 'entities/auth'; -import YandexIcon from 'public/yandex-logo.svg'; -import VkontakteIcon from 'public/vkontakte-logo.svg'; -import GoogleIcon from 'public/google-logo.svg'; -import GithubIcon from 'public/github-logo.svg'; +import GithubIcon from 'github-logo.svg'; +import GoogleIcon from 'google-logo.svg'; +import VkontakteIcon from 'vkontakte-logo.svg'; +import YandexIcon from 'yandex-logo.svg'; +import { OAuthProvider } from '../model/types'; export type OAuthProviderMeta = { iconSrc: string; - buttonClassName?: string; + className?: string; }; -export const OAUTH_PROVIDERS: Record = { +export const OAUTH_PROVIDERS: Record = { yandex: { iconSrc: YandexIcon, - buttonClassName: 'text-[#fc3f1d] hover:text-[#fc3f1d]', + className: 'text-[#fc3f1d] hover:text-[#fc3f1d]', }, vkontakte: { iconSrc: VkontakteIcon, - buttonClassName: 'bg-[#07f] hover:bg-[#07f]', + className: 'bg-[#07f] hover:bg-[#07f]', }, google: { iconSrc: GoogleIcon }, github: { iconSrc: GithubIcon, - buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] text-white hover:text-white ', + className: 'bg-[#24292f] hover:bg-[#24292f] text-white hover:text-white ', }, } as const; diff --git a/src/entities/auth/model/schemas.ts b/src/entities/auth/model/schemas.ts index c56d2f3..e8f58ab 100644 --- a/src/entities/auth/model/schemas.ts +++ b/src/entities/auth/model/schemas.ts @@ -111,3 +111,13 @@ export const ResendCodeResponse = GlobalSuccess.extend({ retryAfterSeconds: z.number(), retries: z.number(), }); + +export const ExchangeTokenResponse = GlobalSuccess.extend({ + access: z.string(), + isNewUser: z.boolean(), + provider: OAuthProvider, +}); +export const ExchangeTokenBody = z.object({ + token: z.string(), + provider: OAuthProvider, +}); diff --git a/src/entities/auth/model/types.ts b/src/entities/auth/model/types.ts index af7dfe6..9b14eda 100644 --- a/src/entities/auth/model/types.ts +++ b/src/entities/auth/model/types.ts @@ -28,3 +28,6 @@ export type ConnectOAuthProviderResponse = z.infer; export type ResendCodeBody = z.infer; export type ResendCodeResponse = z.infer; + +export type ExchangeTokenBody = z.infer; +export type ExchangeTokenResponse = z.infer; diff --git a/src/features/auth/oauth-login/model/types.ts b/src/features/auth/oauth-login/model/types.ts index da8f909..8784ad2 100644 --- a/src/features/auth/oauth-login/model/types.ts +++ b/src/features/auth/oauth-login/model/types.ts @@ -1,4 +1,4 @@ -import { TAuth } from 'entities/auth'; +import { type TAuth } from 'entities/auth'; export type StartOauthParams = { provider: TAuth.OAuthProvider; diff --git a/src/features/auth/oauth-login/ui/OAuthButton.tsx b/src/features/auth/oauth-login/ui/OAuthButton.tsx deleted file mode 100644 index fde9e03..0000000 --- a/src/features/auth/oauth-login/ui/OAuthButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import Link from 'next/link'; -import Image from 'next/image'; -import { Button } from 'shared/ui'; -import { cn } from 'shared/lib/utils'; -import { OAUTH_PROVIDERS } from 'entities/auth'; -import type { ButtonHTMLAttributes } from 'react'; -import type { Route } from 'next'; -import type { TAuth } from 'entities/auth'; - -type OAuthButtonProps = Omit, 'children'> & { - iconClassName?: string; - href: Route; - data: { - label: string; - value: TAuth.OAuthProvider; - }; -}; - -export function OAuthButton({ className, iconClassName, href, data, ...props }: OAuthButtonProps) { - const { label, value } = data; - const meta = OAUTH_PROVIDERS[value]; - - if (!meta) return null; - - return ( - - ); -} diff --git a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx index ab8a14e..7c9add1 100644 --- a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx @@ -1,33 +1,16 @@ -'use client'; +import dynamic from 'next/dynamic'; import { Skeleton } from 'shared/ui'; -import { cn } from 'shared/lib/utils'; -import { useQuery } from '@tanstack/react-query'; -import { AuthQueries } from 'entities/auth'; -import { routes } from 'shared/config'; -import { OAuthButton } from './OAuthButton'; -import { type TAuth } from 'entities/auth'; -import { type StartOauthParams } from '../model/types'; -import { type Route } from 'next'; -export const getRoute = (provider: TAuth.OAuthProvider) => { - const params = new URLSearchParams({ - provider, - startOAuth: 'true', - } satisfies Record); - - return `${routes.auth.oauth()}?${params.toString()}`; -}; - -export function OAuthLoginButtons({ className }: { className?: string }) { - const { data, isLoading } = useQuery(AuthQueries.getOAuthProviders()); - - return ( -
- {isLoading && Array.from({ length: 3 }, (_v, i) => )} - - {data?.map((item) => { - return ; - })} -
- ); -} +export const OAuthLoginButtons = dynamic( + () => import('./OAuthLoginButtonsContent').then((module) => module.OAuthLoginButtonsContent), + { + ssr: false, + loading: () => ( +
+ {Array.from({ length: 3 }, (_v, i) => ( + + ))} +
+ ), + } +); diff --git a/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx b/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx new file mode 100644 index 0000000..d0d1e38 --- /dev/null +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { useSuspenseQuery } from '@tanstack/react-query'; +import { AuthQueries, OAUTH_PROVIDERS, type TAuth } from 'entities/auth'; +import Image from 'next/image'; +import { useCallback } from 'react'; +import { routes } from 'shared/config'; +import { cn } from 'shared/lib/utils'; +import { Button } from 'shared/ui'; + +export interface OAuthLoginButtonsContentProps { + className?: string; +} + +export function OAuthLoginButtonsContent({ className }: OAuthLoginButtonsContentProps) { + const { data } = useSuspenseQuery(AuthQueries.getOAuthProviders()); + + const startOAuth = useCallback((provider: TAuth.OAuthProvider) => { + const route = new URL(routes.auth.oauth(), window.location.origin); + + route.searchParams.set('provider', provider); + route.searchParams.set('startOAuth', 'true'); + + window.location.assign(route.href); + }, []); + + return ( +
+ {data?.map(({ label, value }) => { + const data = OAUTH_PROVIDERS[value]; + + return ( + + ); + })} +
+ ); +} diff --git a/src/features/auth/oauth-login/ui/OAuthSeparator.tsx b/src/features/auth/oauth-login/ui/OAuthSeparator.tsx index 7522746..b635d93 100644 --- a/src/features/auth/oauth-login/ui/OAuthSeparator.tsx +++ b/src/features/auth/oauth-login/ui/OAuthSeparator.tsx @@ -1,16 +1,16 @@ -import { cn } from 'shared/lib/utils'; +import dynamic from 'next/dynamic'; +import { Skeleton } from 'shared/ui'; -interface OAuthSeparatorProps { - className?: string; - label?: string; -} - -export function OAuthSeparator({ className, label = 'или' }: OAuthSeparatorProps) { - return ( -
- - {label} - -
- ); -} +export const OAuthSeparator = dynamic( + () => import('./OAuthSeparatorContent').then((module) => module.OAuthSeparatorContent), + { + ssr: false, + loading: () => ( +
+ + + +
+ ), + } +); diff --git a/src/features/auth/oauth-login/ui/OAuthSeparatorContent.tsx b/src/features/auth/oauth-login/ui/OAuthSeparatorContent.tsx new file mode 100644 index 0000000..268b3f2 --- /dev/null +++ b/src/features/auth/oauth-login/ui/OAuthSeparatorContent.tsx @@ -0,0 +1,16 @@ +import { cn } from 'shared/lib/utils'; + +export interface OAuthSeparatorProps { + className?: string; + label?: string; +} + +export function OAuthSeparatorContent({ className, label = 'или' }: OAuthSeparatorProps) { + return ( +
+ + {label} + +
+ ); +} diff --git a/src/features/auth/sign-out/model/useSignOut.ts b/src/features/auth/sign-out/model/useSignOut.ts index 0b13baa..e69a88f 100644 --- a/src/features/auth/sign-out/model/useSignOut.ts +++ b/src/features/auth/sign-out/model/useSignOut.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; import { useRouter } from 'next/navigation'; import { AccessToken } from 'shared/api'; import { routes } from 'shared/config'; diff --git a/src/features/otp-form/model/useResend.ts b/src/features/otp-form/model/useResend.ts index 85ed2ac..c9e23ed 100644 --- a/src/features/otp-form/model/useResend.ts +++ b/src/features/otp-form/model/useResend.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseResendOptions = Omit< UseMutationOptions, diff --git a/src/features/otp-form/ui/ResendCodeControl.tsx b/src/features/otp-form/ui/ResendCodeControl.tsx index a5b04fb..4fb4d11 100644 --- a/src/features/otp-form/ui/ResendCodeControl.tsx +++ b/src/features/otp-form/ui/ResendCodeControl.tsx @@ -6,7 +6,7 @@ import { classNames, formatTime } from 'shared/lib/utils'; import { Button } from 'shared/ui'; import { RESEND_CODE_DELAY_MS } from '../model/const'; import { useResendCode, UseResendOptions } from '../model/useResend'; -import { TAuth } from 'entities/auth'; +import { type TAuth } from 'entities/auth'; import { toast } from 'sonner'; interface ResendCodeControlProps extends Omit, 'children'> { diff --git a/src/features/teams/active-team/model/useSwitchTeam.ts b/src/features/teams/active-team/model/useSwitchTeam.ts index 050f851..6ca72e5 100644 --- a/src/features/teams/active-team/model/useSwitchTeam.ts +++ b/src/features/teams/active-team/model/useSwitchTeam.ts @@ -1,7 +1,7 @@ 'use client'; import { useTeamStore } from 'entities/team'; -import { TUser } from 'entities/user'; +import { type TUser } from 'entities/user'; import { useRouter } from 'next/navigation'; import { useCallback } from 'react'; import { routes } from 'shared/config'; diff --git a/src/features/teams/invite/model/useInviteTeamMember.ts b/src/features/teams/invite/model/useInviteTeamMember.ts index fae58e7..c5c8f9e 100644 --- a/src/features/teams/invite/model/useInviteTeamMember.ts +++ b/src/features/teams/invite/model/useInviteTeamMember.ts @@ -13,12 +13,12 @@ export function useInviteTeamMember({ onSuccess, ...rest }: UseInviteTeamMemberO return useMutation({ ...rest, mutationFn: ({ teamId, body }) => TeamHttp.inviteMember(teamId, body), - onSuccess: async (res, _v, _r, context) => { - onSuccess?.(res, _v, _r, context); + onSuccess: async (res, v, _r, context) => { + onSuccess?.(res, v, _r, context); toast.success(res.message ?? 'Приглашение отправлено'); - if (_v.teamId) { - await context.client.invalidateQueries({ queryKey: teamFabricKeys.invitations(_v.teamId) }); + if (v.teamId) { + await context.client.invalidateQueries({ queryKey: teamFabricKeys.invitations(v.teamId) }); } }, }); diff --git a/src/features/upload-avatar/model/useUploadAvatar.ts b/src/features/upload-avatar/model/useUploadAvatar.ts index 070572c..7d88a99 100644 --- a/src/features/upload-avatar/model/useUploadAvatar.ts +++ b/src/features/upload-avatar/model/useUploadAvatar.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AssetHttp, TAsset } from 'entities/asset'; +import { AssetHttp, type TAsset } from 'entities/asset'; import { toast } from 'sonner'; export type UseUploadFileOptions = Omit< diff --git a/src/features/upload-avatar/ui/UploadAvatar.tsx b/src/features/upload-avatar/ui/UploadAvatar.tsx index 74fe1d9..e030787 100644 --- a/src/features/upload-avatar/ui/UploadAvatar.tsx +++ b/src/features/upload-avatar/ui/UploadAvatar.tsx @@ -1,4 +1,4 @@ -import { TAsset } from 'entities/asset'; +import { type TAsset } from 'entities/asset'; import { Pencil } from 'lucide-react'; import { type ChangeEvent, type ComponentProps, type ReactElement, useRef } from 'react'; import { classNames } from 'shared/lib/utils'; diff --git a/src/pages/auth/forgot-password/model/useResetPassword.ts b/src/pages/auth/forgot-password/model/useResetPassword.ts index b819e10..3d2c92f 100644 --- a/src/pages/auth/forgot-password/model/useResetPassword.ts +++ b/src/pages/auth/forgot-password/model/useResetPassword.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseResetePasswordOptions = Omit< UseMutationOptions, diff --git a/src/pages/auth/forgot-password/model/useSendCode.ts b/src/pages/auth/forgot-password/model/useSendCode.ts index aa0d66b..940674f 100644 --- a/src/pages/auth/forgot-password/model/useSendCode.ts +++ b/src/pages/auth/forgot-password/model/useSendCode.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseSendCodeOptions = Omit< UseMutationOptions< diff --git a/src/pages/auth/forgot-password/model/useSendPassword.ts b/src/pages/auth/forgot-password/model/useSendPassword.ts index ee38928..62b3b60 100644 --- a/src/pages/auth/forgot-password/model/useSendPassword.ts +++ b/src/pages/auth/forgot-password/model/useSendPassword.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseSendPasswordOptions = Omit< UseMutationOptions< diff --git a/src/pages/auth/forgot-password/ui/EmailForm.tsx b/src/pages/auth/forgot-password/ui/EmailForm.tsx index d266b10..d0217bc 100644 --- a/src/pages/auth/forgot-password/ui/EmailForm.tsx +++ b/src/pages/auth/forgot-password/ui/EmailForm.tsx @@ -13,7 +13,7 @@ import { FieldLabel, InputEmail, } from 'shared/ui'; -import { TAuth } from 'entities/auth'; +import { type TAuth } from 'entities/auth'; import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import type { EmailFormValues } from '../model/types'; diff --git a/src/pages/auth/forgot-password/ui/PasswordForm.tsx b/src/pages/auth/forgot-password/ui/PasswordForm.tsx index d248826..80baa72 100644 --- a/src/pages/auth/forgot-password/ui/PasswordForm.tsx +++ b/src/pages/auth/forgot-password/ui/PasswordForm.tsx @@ -14,7 +14,7 @@ import { InputPassword, } from 'shared/ui'; import { ComponentProps, useState } from 'react'; -import { TAuth } from 'entities/auth'; +import { type TAuth } from 'entities/auth'; import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { setFormErrors } from 'shared/lib/utils'; diff --git a/src/pages/auth/oauth/index.ts b/src/pages/auth/oauth/index.ts deleted file mode 100644 index daa0dd4..0000000 --- a/src/pages/auth/oauth/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { OAuthPage } from './ui/OAuthPage'; diff --git a/src/pages/auth/oauth/ui/OAuthPage.tsx b/src/pages/auth/oauth/ui/OAuthPage.tsx deleted file mode 100644 index 16613be..0000000 --- a/src/pages/auth/oauth/ui/OAuthPage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { type TAuth } from 'entities/auth'; -import { StartOauthParams } from 'features/auth/oauth-login'; -import { Route } from 'next'; -import { redirect } from 'next/navigation'; -import { routes } from 'shared/config'; -import { env } from 'shared/config'; - -type BooleanRaw = 'false' | 'true'; -type OAuthParams = { - success: BooleanRaw; - message: string; - access: string; - provider: TAuth.OAuthProvider; - isNewUser: BooleanRaw; -} & StartOauthParams; - -interface Props { - searchParams: Promise>; -} - -export async function OAuthPage({ searchParams }: Props) { - const { success, message, provider, startOAuth } = await searchParams; - - // TODO: страница знает API - - if (provider && startOAuth === 'true') { - redirect(`${env.NEXT_PUBLIC_API_BASE_URL}/auth/oauth/${provider}` as Route); - } - - if (!success) { - redirect(routes.auth.signin()); - } - - if (success === 'true') { - redirect( - `${routes.user.profile()}?success=true&message=${encodeURIComponent(message || 'Вход успешен')}` - ); - } - - const errorUrl = message - ? `${routes.auth.signin()}?success=true&message=${encodeURIComponent(message)}` - : routes.auth.signin(); - - redirect(errorUrl as Route); -} diff --git a/src/pages/auth/signin/model/useSignin.ts b/src/pages/auth/signin/model/useSignin.ts index 69ad33d..4028022 100644 --- a/src/pages/auth/signin/model/useSignin.ts +++ b/src/pages/auth/signin/model/useSignin.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseSigninOptions = Omit< UseMutationOptions, diff --git a/src/pages/auth/signin/ui/SigninForm.tsx b/src/pages/auth/signin/ui/SigninForm.tsx index 6fa91e4..ad65507 100644 --- a/src/pages/auth/signin/ui/SigninForm.tsx +++ b/src/pages/auth/signin/ui/SigninForm.tsx @@ -1,9 +1,13 @@ 'use client'; -import { Controller, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import { SigninForm as SigninFormSchema } from '../model/schemas'; -import type { SigninFormValues } from '../model/types'; +import { type TAuth } from 'entities/auth'; +import { OAuthLoginButtons, OAuthSeparator } from 'features/auth/oauth-login'; +import { ComponentProps } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { extractValidationIssues } from 'shared/api'; +import { routes } from 'shared/config'; +import { cn, setFormErrors } from 'shared/lib/utils'; import { Button, Card, @@ -20,14 +24,9 @@ import { InputPassword, Link, } from 'shared/ui'; -import { cn, setFormErrors } from 'shared/lib/utils'; -import { routes } from 'shared/config'; -import { extractValidationIssues } from 'shared/api'; -import { TAuth } from 'entities/auth'; -import { ComponentProps, Suspense } from 'react'; +import { SigninForm as SigninFormSchema } from '../model/schemas'; +import type { SigninFormValues } from '../model/types'; import { useSignin, UseSigninOptions } from '../model/useSignin'; -import { OAuthLoginButtons, OAuthSeparator } from 'features/auth/oauth-login'; -import { QueryParamsHandler } from 'features/handle-query-params'; interface SigninFormProps extends Omit, 'children' | 'onSubmit'> { mutateOptions?: UseSigninOptions; @@ -61,9 +60,6 @@ export function SigninForm({ className, mutateOptions = {}, ...props }: SigninFo return ( <> - - - Вход в систему diff --git a/src/pages/auth/signup/model/useSignup.ts b/src/pages/auth/signup/model/useSignup.ts index 6061d9d..8d6ac59 100644 --- a/src/pages/auth/signup/model/useSignup.ts +++ b/src/pages/auth/signup/model/useSignup.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseSignupOptions = Omit< UseMutationOptions, diff --git a/src/pages/auth/signup/model/useSignupConfirm.ts b/src/pages/auth/signup/model/useSignupConfirm.ts index 6ce7d74..fbc908c 100644 --- a/src/pages/auth/signup/model/useSignupConfirm.ts +++ b/src/pages/auth/signup/model/useSignupConfirm.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AuthHttp, TAuth } from 'entities/auth'; +import { AuthHttp, type TAuth } from 'entities/auth'; export type UseSignupConfirmOptions = Omit< UseMutationOptions, diff --git a/src/pages/auth/signup/model/utils/field-name-mapper.ts b/src/pages/auth/signup/model/utils/field-name-mapper.ts index 144cbde..b6c6992 100644 --- a/src/pages/auth/signup/model/utils/field-name-mapper.ts +++ b/src/pages/auth/signup/model/utils/field-name-mapper.ts @@ -1,6 +1,6 @@ import type { FieldPath } from 'react-hook-form'; import type { SignupFormValues } from '../types'; -import { TAuth } from 'entities/auth'; +import { type TAuth } from 'entities/auth'; export const fieldNameMapper = ( fieldName: FieldPath diff --git a/src/pages/auth/signup/ui/SignupForm.tsx b/src/pages/auth/signup/ui/SignupForm.tsx index dc74434..b69e8ae 100644 --- a/src/pages/auth/signup/ui/SignupForm.tsx +++ b/src/pages/auth/signup/ui/SignupForm.tsx @@ -28,7 +28,7 @@ import { ComponentProps, useState } from 'react'; import { fieldNameMapper } from '../model/utils/field-name-mapper'; import { prepareFullName } from '../model/utils/prepare-fullname'; import { extractValidationIssues } from 'shared/api'; -import { TAuth } from 'entities/auth'; +import { type TAuth } from 'entities/auth'; import { useSignup, type UseSignupOptions } from '../model/useSignup'; import { OAuthLoginButtons, OAuthSeparator } from 'features/auth/oauth-login'; diff --git a/src/pages/invitations/ui/InvitationCard.tsx b/src/pages/invitations/ui/InvitationCard.tsx index 4a76a0e..e621440 100644 --- a/src/pages/invitations/ui/InvitationCard.tsx +++ b/src/pages/invitations/ui/InvitationCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { TUser } from 'entities/user'; +import { type TUser } from 'entities/user'; import { MailIcon } from 'lucide-react'; import { useAcceptTeamInvitation } from '../api/useAcceptTeamInvitation'; import { formatDate } from 'shared/lib/utils'; diff --git a/src/pages/profile/api/useUpdateNotifications.ts b/src/pages/profile/api/useUpdateNotifications.ts index 63a7554..7be5efa 100644 --- a/src/pages/profile/api/useUpdateNotifications.ts +++ b/src/pages/profile/api/useUpdateNotifications.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { TUser, userFabricKeys, UserHttp } from 'entities/user'; +import { type TUser, userFabricKeys, UserHttp } from 'entities/user'; import { toast } from 'sonner'; type UseUpdateNotificationsProps = Omit< diff --git a/src/pages/profile/model/notifications.ts b/src/pages/profile/model/notifications.ts index e1bdb4f..6c7be57 100644 --- a/src/pages/profile/model/notifications.ts +++ b/src/pages/profile/model/notifications.ts @@ -1,4 +1,4 @@ -import { TUser } from 'entities/user'; +import { type TUser } from 'entities/user'; export type Notifications = TUser.UserResponse['notifications']; export type NotificationChannel = keyof Notifications; diff --git a/src/pages/profile/model/useMePage.ts b/src/pages/profile/model/useMePage.ts index 489b660..a9fb11d 100644 --- a/src/pages/profile/model/useMePage.ts +++ b/src/pages/profile/model/useMePage.ts @@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery } from '@tanstack/react-query'; -import { TUser, UserQueries } from 'entities/user'; +import { type TUser, UserQueries } from 'entities/user'; import { useEffect } from 'react'; import { useForm, useFormState } from 'react-hook-form'; import { useUpdateProfile } from '../api/useUpdateProfile'; diff --git a/src/pages/profile/ui/me-page/IdentityItem.tsx b/src/pages/profile/ui/me-page/IdentityItem.tsx index bd5ce94..e7475c0 100644 --- a/src/pages/profile/ui/me-page/IdentityItem.tsx +++ b/src/pages/profile/ui/me-page/IdentityItem.tsx @@ -3,7 +3,7 @@ import { Item, ItemActions, ItemContent, ItemMedia } from 'shared/ui'; import { UploadAvatar } from 'features/upload-avatar'; import { SignOut } from 'features/auth/sign-out'; -import { TUser, UserAvatar } from 'entities/user'; +import { type TUser, UserAvatar } from 'entities/user'; type AccountIdentityItemProps = { profile: TUser.UserResponse['profile']; diff --git a/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx b/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx index e505e4f..1744497 100644 --- a/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx +++ b/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx @@ -1,5 +1,5 @@ import { OAuthManageButton } from './OAuthManageButton'; -import { useConnectedAccounts } from 'pages/profile/api/useConnectedAccounts'; +import { useConnectedAccounts } from '../../../api/useConnectedAccounts'; import { CardSection } from 'shared/ui'; export function AccountSection() { diff --git a/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx b/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx index dd3f024..d2a3d4f 100644 --- a/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx +++ b/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx @@ -1,7 +1,7 @@ 'use client'; -import { OAUTH_PROVIDERS, TAuth, authFabricKeys } from 'entities/auth'; -import { useCallback, type ComponentProps } from 'react'; +import { authFabricKeys, OAUTH_PROVIDERS, type TAuth } from 'entities/auth'; +import { type ComponentProps, useCallback } from 'react'; import { Button } from 'shared/ui'; import { useConnectOAuthProvider } from '../../../api/useConnectOauthProvider'; import { useDisconnectOAuthProvider } from '../../../api/useDisconnectOauthProvider'; @@ -52,7 +52,7 @@ export function OAuthManageButton({ provider, label, isLinked, ...props }: OAuth disabled={isLoading} {...props} > -
+
{label}
diff --git a/src/pages/team/api/useUploadCover.ts b/src/pages/team/api/useUploadCover.ts index 9062ff5..b5d0649 100644 --- a/src/pages/team/api/useUploadCover.ts +++ b/src/pages/team/api/useUploadCover.ts @@ -1,5 +1,5 @@ import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; -import { AssetHttp, TAsset } from 'entities/asset'; +import { AssetHttp, type TAsset } from 'entities/asset'; import { toast } from 'sonner'; export type UseUploadFileOptions = Omit< diff --git a/src/pages/team/config/member.ts b/src/pages/team/config/member.ts index 5b5db8d..c636e76 100644 --- a/src/pages/team/config/member.ts +++ b/src/pages/team/config/member.ts @@ -1,4 +1,4 @@ -import { TTeam } from 'entities/team'; +import { type TTeam } from 'entities/team'; import { ComponentProps } from 'react'; import { Badge } from 'shared/ui'; diff --git a/src/pages/team/model/useMembersPage.ts b/src/pages/team/model/useMembersPage.ts index fab201a..1e472ee 100644 --- a/src/pages/team/model/useMembersPage.ts +++ b/src/pages/team/model/useMembersPage.ts @@ -1,7 +1,7 @@ 'use client'; import { useQuery } from '@tanstack/react-query'; -import { TeamQueries, TTeam, useTeamStore } from 'entities/team'; +import { TeamQueries, type TTeam, useTeamStore } from 'entities/team'; import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'; import { debounce } from 'shared/lib/utils'; diff --git a/src/pages/team/ui/invitations/InvitationCard.tsx b/src/pages/team/ui/invitations/InvitationCard.tsx index c758ce5..72b1e2a 100644 --- a/src/pages/team/ui/invitations/InvitationCard.tsx +++ b/src/pages/team/ui/invitations/InvitationCard.tsx @@ -1,4 +1,4 @@ -import { TTeam } from 'entities/team'; +import { type TTeam } from 'entities/team'; import { Clock, Copy, MailIcon, RotateCw, X } from 'lucide-react'; import { ComponentProps } from 'react'; import { classNames, formatDate } from 'shared/lib/utils'; diff --git a/src/pages/team/ui/invitations/InvitationRoleSelect.tsx b/src/pages/team/ui/invitations/InvitationRoleSelect.tsx index ddde337..2803897 100644 --- a/src/pages/team/ui/invitations/InvitationRoleSelect.tsx +++ b/src/pages/team/ui/invitations/InvitationRoleSelect.tsx @@ -1,4 +1,4 @@ -import { INVITATION_ROLES, ROLE_LABELS, TTeam } from 'entities/team'; +import { INVITATION_ROLES, ROLE_LABELS, type TTeam } from 'entities/team'; import { ComponentProps } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'shared/ui'; import { useUpdateInvitation } from '../../api/useUpdateInvitation'; diff --git a/src/pages/team/ui/members/MemberCard.tsx b/src/pages/team/ui/members/MemberCard.tsx index 87bdf4b..8a304ef 100644 --- a/src/pages/team/ui/members/MemberCard.tsx +++ b/src/pages/team/ui/members/MemberCard.tsx @@ -1,4 +1,4 @@ -import { TTeam } from 'entities/team'; +import { type TTeam } from 'entities/team'; import { X } from 'lucide-react'; import { ComponentProps } from 'react'; import { classNames } from 'shared/lib/utils'; diff --git a/src/pages/team/ui/members/MemberRoleSelect.tsx b/src/pages/team/ui/members/MemberRoleSelect.tsx index 3cdda04..65e007a 100644 --- a/src/pages/team/ui/members/MemberRoleSelect.tsx +++ b/src/pages/team/ui/members/MemberRoleSelect.tsx @@ -1,4 +1,4 @@ -import { INVITATION_ROLES, ROLE_LABELS, TTeam } from 'entities/team'; +import { INVITATION_ROLES, ROLE_LABELS, type TTeam } from 'entities/team'; import { ComponentProps } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'shared/ui'; import { useUpdateMember } from '../../api/useUpdateMember'; diff --git a/src/pages/team/ui/members/MemberStatusSelect.tsx b/src/pages/team/ui/members/MemberStatusSelect.tsx index f4e1ee2..597a5c3 100644 --- a/src/pages/team/ui/members/MemberStatusSelect.tsx +++ b/src/pages/team/ui/members/MemberStatusSelect.tsx @@ -1,4 +1,4 @@ -import { MEMBER_STATUSES, STATUS_LABELS, TTeam } from 'entities/team'; +import { MEMBER_STATUSES, STATUS_LABELS, type TTeam } from 'entities/team'; import { ComponentProps } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from 'shared/ui'; import { useUpdateMember } from '../../api/useUpdateMember'; diff --git a/src/pages/team/ui/projects/ProjectCard.tsx b/src/pages/team/ui/projects/ProjectCard.tsx index 757073f..41a5ab9 100644 --- a/src/pages/team/ui/projects/ProjectCard.tsx +++ b/src/pages/team/ui/projects/ProjectCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { projectIconCodeToEmoji, TProject } from 'entities/project'; +import { projectIconCodeToEmoji, type TProject } from 'entities/project'; import { useTeamStore } from 'entities/team'; import { ArchiveProjectDialog, RestoreProjectDialog } from 'features/projects/archive'; import { RemoveProjectDialog } from 'features/projects/remove'; diff --git a/src/pages/team/ui/settings/SaveBar.tsx b/src/pages/team/ui/settings/SaveBar.tsx index 52cb744..0992d04 100644 --- a/src/pages/team/ui/settings/SaveBar.tsx +++ b/src/pages/team/ui/settings/SaveBar.tsx @@ -1,4 +1,4 @@ -import { TTeam } from 'entities/team'; +import { type TTeam } from 'entities/team'; import { useFormContext, useFormState } from 'react-hook-form'; import { FloatingSaveBar } from 'shared/ui'; import { useUpdateTeam } from '../../api/useUpdateTeam'; diff --git a/src/pages/team/ui/settings/TeamIdentity.tsx b/src/pages/team/ui/settings/TeamIdentity.tsx index d6bcbb7..29c6ed7 100644 --- a/src/pages/team/ui/settings/TeamIdentity.tsx +++ b/src/pages/team/ui/settings/TeamIdentity.tsx @@ -1,7 +1,7 @@ 'use client'; import { UploadAvatar } from 'features/upload-avatar'; -import { TeamAvatar, TTeam } from 'entities/team'; +import { TeamAvatar, type TTeam } from 'entities/team'; import { CardSection, Separator } from 'shared/ui'; import { TeamCover } from './TeamCover'; import { TeamIdentityForm } from './TeamIdentityForm'; diff --git a/src/pages/teams/ui/TeamCard.tsx b/src/pages/teams/ui/TeamCard.tsx index 204f9fd..31645ee 100644 --- a/src/pages/teams/ui/TeamCard.tsx +++ b/src/pages/teams/ui/TeamCard.tsx @@ -1,7 +1,7 @@ 'use client'; import { TeamAvatar, useTeamStore } from 'entities/team'; -import { TUser } from 'entities/user'; +import { type TUser } from 'entities/user'; import { Crown } from 'lucide-react'; import { classNames } from 'shared/lib/utils'; import { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle } from 'shared/ui'; diff --git a/src/pages/teams/ui/TeamCardActions.tsx b/src/pages/teams/ui/TeamCardActions.tsx index 6d8e8d4..9c4c46d 100644 --- a/src/pages/teams/ui/TeamCardActions.tsx +++ b/src/pages/teams/ui/TeamCardActions.tsx @@ -1,6 +1,6 @@ 'use client'; -import { TUser } from 'entities/user'; +import { type TUser } from 'entities/user'; import { InviteTeamMemberDialog } from 'features/teams/invite'; import { RemoveTeamDialog } from 'features/teams/remove'; import { MoreHorizontal, Trash2, UserPlus } from 'lucide-react'; diff --git a/src/widgets/app-sidebar/ui/projects/ProjectActions.tsx b/src/widgets/app-sidebar/ui/projects/ProjectActions.tsx index 1686598..5143651 100644 --- a/src/widgets/app-sidebar/ui/projects/ProjectActions.tsx +++ b/src/widgets/app-sidebar/ui/projects/ProjectActions.tsx @@ -1,6 +1,6 @@ 'use client'; -import { TProject } from 'entities/project'; +import { type TProject } from 'entities/project'; import { ArchiveProjectDialog, RestoreProjectDialog } from 'features/projects/archive'; import { ShareProjectDialog } from 'features/projects/share'; import { Archive, Link2 } from 'lucide-react'; diff --git a/src/widgets/app-sidebar/ui/projects/ProjectsContent.tsx b/src/widgets/app-sidebar/ui/projects/ProjectsContent.tsx index 2128041..c08eb73 100644 --- a/src/widgets/app-sidebar/ui/projects/ProjectsContent.tsx +++ b/src/widgets/app-sidebar/ui/projects/ProjectsContent.tsx @@ -26,12 +26,15 @@ export function ProjectsContent() { const teamId = useTeamStore.use.teamId(); const router = useRouter(); const pathname = usePathname(); + const { open, isMobile } = useSidebar(); const projects = useQuery({ ...ProjectQueries.getProjects(teamId!), enabled: !!teamId }); - const projectList = projects.data?.items.slice(0, 6) ?? []; - const totalProjects = projects.data?.items.length ?? 0; - const { open, isMobile } = useSidebar(); + if (!projects.data) { + return null; + } + const projectList = projects.data?.items.slice(0, 6) ?? []; + const totalProjects = projects.data?.items.length ?? 0; const isAllowedToHighlight = !open && !isMobile; const handleClickTrigger = () => { @@ -40,10 +43,6 @@ export function ProjectsContent() { } }; - if (!projects.data) { - return null; - } - return ( diff --git a/src/widgets/app-sidebar/ui/teams/TeamTrigger.tsx b/src/widgets/app-sidebar/ui/teams/TeamTrigger.tsx index abb90a8..f572879 100644 --- a/src/widgets/app-sidebar/ui/teams/TeamTrigger.tsx +++ b/src/widgets/app-sidebar/ui/teams/TeamTrigger.tsx @@ -1,6 +1,6 @@ import { UseQueryResult } from '@tanstack/react-query'; import { useTeamStore } from 'entities/team'; -import { TUser } from 'entities/user'; +import { type TUser } from 'entities/user'; import { ChevronsUpDown } from 'lucide-react'; import { useMemo } from 'react'; import { TeamItem } from './TeamItem';