From 05a1e0dfafe757659b4d416e945bcef488c4c14d Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Tue, 16 Jun 2026 15:24:59 +0300 Subject: [PATCH 1/4] refactor: update OAuth API endpoint URLs --- src/entities/auth/api/http.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/entities/auth/api/http.ts b/src/entities/auth/api/http.ts index 959f5a1..05ea242 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: { From 7aec8a36d96302b5ecd04de87a5b1f9a6b270260 Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Tue, 16 Jun 2026 20:30:59 +0300 Subject: [PATCH 2/4] refactor: restructure OAuth authentication flow --- app/(auth)/oauth/page.tsx | 1 - app/(auth)/oauth/route.ts | 94 +++++++++++++++++++ src/entities/auth/api/http.ts | 7 ++ src/entities/auth/model/schemas.ts | 10 ++ src/entities/auth/model/types.ts | 3 + src/features/auth/oauth-login/model/types.ts | 4 +- .../auth/oauth-login/ui/OAuthButton.tsx | 4 +- .../auth/oauth-login/ui/OAuthLoginButtons.tsx | 3 +- src/pages/auth/oauth/index.ts | 1 - src/pages/auth/oauth/ui/OAuthPage.tsx | 45 --------- 10 files changed, 119 insertions(+), 53 deletions(-) delete mode 100644 app/(auth)/oauth/page.tsx create mode 100644 app/(auth)/oauth/route.ts delete mode 100644 src/pages/auth/oauth/index.ts delete mode 100644 src/pages/auth/oauth/ui/OAuthPage.tsx 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..2550681 --- /dev/null +++ b/app/(auth)/oauth/route.ts @@ -0,0 +1,94 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { type TAuth } from 'entities/auth'; +import { StartOauthParams } from 'features/auth/oauth-login'; +import { routes } from 'shared/config'; +import { env } from 'shared/config'; +import { redirect } from 'next/navigation'; +import { cookies as nextCookies } from 'next/headers'; + +type BooleanRaw = 'false' | 'true'; +type ExchangeParams = { token?: string }; +type OAuthParams = { + success?: BooleanRaw; + message?: string; + provider?: TAuth.OAuthProvider; + isNewUser?: BooleanRaw; +} & StartOauthParams & + ExchangeParams; + +const PROVIDER_KEY = 'provider'; + +function addResponseSetCookies(response: Response | NextResponse, cookies: string[]) { + cookies.forEach((cookie) => { + response.headers.append('Set-Cookie', cookie); + }); +} + +export async function GET(request: NextRequest) { + const params: Partial = Object.fromEntries(request.nextUrl.searchParams); + const { provider, startOAuth, token } = params; + const cookieStore = await nextCookies(); + + if (provider && startOAuth === 'true') { + cookieStore.set(PROVIDER_KEY, provider); + const redirectUrl = `${env.NEXT_PUBLIC_API_BASE_URL}/oauth/${provider}`; + return NextResponse.redirect(redirectUrl); + } + + let message = params.message; + let success = params.success; + let cookies: string[] = []; + const APP_URL = request.nextUrl.origin; + + // Exchange token + const providerFromCookie = cookieStore.get(PROVIDER_KEY)?.value; + + if (token) { + try { + const response = await fetch(`${env.NEXT_PUBLIC_API_BASE_URL}/oauth/exchange`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ token, provider: providerFromCookie }), + }); + + const setCookieHeaders = response.headers.getSetCookie(); + if (setCookieHeaders && setCookieHeaders.length > 0) { + cookies = setCookieHeaders; + } + + const data: TAuth.ExchangeTokenResponse = await response.json(); + message = data.message || message; + success = data.success ? 'true' : 'false'; + } catch { + success = 'false'; + } + } + cookieStore.delete(PROVIDER_KEY); + // Ошибка + if (success === 'false') { + const errorUrl = message + ? `${APP_URL}${routes.auth.signin()}?error=${encodeURIComponent(message)}` + : `${APP_URL}${routes.auth.signin()}`; + + const response = NextResponse.redirect(errorUrl); + + addResponseSetCookies(response, cookies); + + return response; + } + + // Успех + if (success === 'true') { + const successUrl = `${APP_URL}${routes.user.profile()}?success=true&message=${encodeURIComponent(message || 'Операция выполнена успешно')}`; + + const response = NextResponse.redirect(successUrl); + addResponseSetCookies(response, cookies); + + return response; + } + + // Fallback + redirect(routes.auth.signin()); +} diff --git a/src/entities/auth/api/http.ts b/src/entities/auth/api/http.ts index 05ea242..170d346 100644 --- a/src/entities/auth/api/http.ts +++ b/src/entities/auth/api/http.ts @@ -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/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..c37876f 100644 --- a/src/features/auth/oauth-login/model/types.ts +++ b/src/features/auth/oauth-login/model/types.ts @@ -1,6 +1,6 @@ import { TAuth } from 'entities/auth'; export type StartOauthParams = { - provider: TAuth.OAuthProvider; - startOAuth: 'true' | 'false'; + provider?: TAuth.OAuthProvider; + startOAuth?: 'true' | 'false'; }; diff --git a/src/features/auth/oauth-login/ui/OAuthButton.tsx b/src/features/auth/oauth-login/ui/OAuthButton.tsx index fde9e03..f8a193f 100644 --- a/src/features/auth/oauth-login/ui/OAuthButton.tsx +++ b/src/features/auth/oauth-login/ui/OAuthButton.tsx @@ -30,7 +30,7 @@ export function OAuthButton({ className, iconClassName, href, data, ...props }: className={cn(meta.buttonClassName, className)} {...props} > - + {meta.iconSrc ? ( + ); } diff --git a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx index ab8a14e..003fce2 100644 --- a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx @@ -3,7 +3,6 @@ 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'; @@ -15,7 +14,7 @@ export const getRoute = (provider: TAuth.OAuthProvider) => { startOAuth: 'true', } satisfies Record); - return `${routes.auth.oauth()}?${params.toString()}`; + return `/oauth?${params.toString()}`; }; export function OAuthLoginButtons({ className }: { className?: string }) { 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); -} From 2c485617b2e0a5f38ab0fd7339876f2b7748462f Mon Sep 17 00:00:00 2001 From: kapitulin24 Date: Fri, 19 Jun 2026 18:38:47 +0300 Subject: [PATCH 3/4] refactor: replace OAuthButton with dynamic loading for improved performance --- app/(auth)/oauth/route.ts | 120 ++++++++---------- src/entities/auth/config/oauth-providers.ts | 20 +-- src/features/auth/oauth-login/model/types.ts | 4 +- .../auth/oauth-login/ui/OAuthButton.tsx | 48 ------- .../auth/oauth-login/ui/OAuthLoginButtons.tsx | 44 ++----- .../ui/OAuthLoginButtonsContent.tsx | 53 ++++++++ .../auth/oauth-login/ui/OAuthSeparator.tsx | 30 ++--- .../oauth-login/ui/OAuthSeparatorContent.tsx | 16 +++ src/pages/auth/signin/ui/SigninForm.tsx | 22 ++-- .../account-section/AccountsSection.tsx | 2 +- .../account-section/OAuthManageButton.tsx | 6 +- 11 files changed, 178 insertions(+), 187 deletions(-) delete mode 100644 src/features/auth/oauth-login/ui/OAuthButton.tsx create mode 100644 src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx create mode 100644 src/features/auth/oauth-login/ui/OAuthSeparatorContent.tsx diff --git a/app/(auth)/oauth/route.ts b/app/(auth)/oauth/route.ts index 2550681..8d1d19f 100644 --- a/app/(auth)/oauth/route.ts +++ b/app/(auth)/oauth/route.ts @@ -1,94 +1,84 @@ -import { NextRequest, NextResponse } from 'next/server'; import { type TAuth } from 'entities/auth'; import { StartOauthParams } from 'features/auth/oauth-login'; -import { routes } from 'shared/config'; -import { env } from 'shared/config'; -import { redirect } from 'next/navigation'; -import { cookies as nextCookies } from 'next/headers'; - -type BooleanRaw = 'false' | 'true'; -type ExchangeParams = { token?: string }; -type OAuthParams = { - success?: BooleanRaw; - message?: string; - provider?: TAuth.OAuthProvider; - isNewUser?: BooleanRaw; -} & StartOauthParams & - ExchangeParams; - -const PROVIDER_KEY = 'provider'; - -function addResponseSetCookies(response: Response | NextResponse, cookies: string[]) { - cookies.forEach((cookie) => { - response.headers.append('Set-Cookie', cookie); - }); -} +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); - const { provider, startOAuth, token } = params; - const cookieStore = await nextCookies(); - if (provider && startOAuth === 'true') { - cookieStore.set(PROVIDER_KEY, provider); + //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); } - let message = params.message; - let success = params.success; - let cookies: string[] = []; - const APP_URL = request.nextUrl.origin; + //exchange token + if ('token' in params && params.token) { + try { + const { token, success, provider } = params; - // Exchange token - const providerFromCookie = cookieStore.get(PROVIDER_KEY)?.value; + if (success === 'false') { + throw new Error(ERROR_MESSAGE); + } + + if (!provider) { + throw new Error('OAuth provider не найден в query параметрах'); + } - if (token) { - try { const response = await fetch(`${env.NEXT_PUBLIC_API_BASE_URL}/oauth/exchange`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ token, provider: providerFromCookie }), + body: JSON.stringify({ token, provider } satisfies TAuth.ExchangeTokenBody), }); - const setCookieHeaders = response.headers.getSetCookie(); - if (setCookieHeaders && setCookieHeaders.length > 0) { - cookies = setCookieHeaders; + const data = (await response.json()) as TAuth.ExchangeTokenResponse; + + if (!response.ok || !data.success) { + throw new Error( + data.message || `OAuth exchange завершился с ошибкой (status ${response.status})` + ); } - const data: TAuth.ExchangeTokenResponse = await response.json(); - message = data.message || message; - success = data.success ? 'true' : 'false'; - } catch { - success = 'false'; - } - } - cookieStore.delete(PROVIDER_KEY); - // Ошибка - if (success === 'false') { - const errorUrl = message - ? `${APP_URL}${routes.auth.signin()}?error=${encodeURIComponent(message)}` - : `${APP_URL}${routes.auth.signin()}`; + const successUrl = new URL(routes.user.profile(), request.url); - const response = NextResponse.redirect(errorUrl); + successUrl.searchParams.set('success', 'true'); + successUrl.searchParams.set('message', 'Операция выполнена успешно'); - addResponseSetCookies(response, cookies); + const res = NextResponse.redirect(successUrl); - return response; - } + (response.headers.getSetCookie() ?? []).forEach((cookie) => { + res.headers.append('Set-Cookie', cookie); + }); - // Успех - if (success === 'true') { - const successUrl = `${APP_URL}${routes.user.profile()}?success=true&message=${encodeURIComponent(message || 'Операция выполнена успешно')}`; + return res; + } catch (error) { + console.error(error instanceof Error ? error.message : ERROR_MESSAGE); + } + } - const response = NextResponse.redirect(successUrl); - addResponseSetCookies(response, cookies); + const errorUrl = new URL(routes.auth.signin(), request.url); - return response; - } + errorUrl.searchParams.set('success', 'false'); + errorUrl.searchParams.set('message', ERROR_MESSAGE); - // Fallback - redirect(routes.auth.signin()); + return NextResponse.redirect(errorUrl); } 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/features/auth/oauth-login/model/types.ts b/src/features/auth/oauth-login/model/types.ts index c37876f..da8f909 100644 --- a/src/features/auth/oauth-login/model/types.ts +++ b/src/features/auth/oauth-login/model/types.ts @@ -1,6 +1,6 @@ import { TAuth } from 'entities/auth'; export type StartOauthParams = { - provider?: TAuth.OAuthProvider; - startOAuth?: 'true' | 'false'; + provider: TAuth.OAuthProvider; + startOAuth: 'true' | 'false'; }; 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 f8a193f..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 003fce2..7c9add1 100644 --- a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx @@ -1,32 +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 { 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 `/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..83e201e --- /dev/null +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx @@ -0,0 +1,53 @@ +'use client'; + +import { useSuspenseQuery } from '@tanstack/react-query'; +import { AuthQueries, OAUTH_PROVIDERS, type TAuth } from 'entities/auth'; +import { Route } from 'next'; +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; +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 router = useRouter(); + + 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'); + + router.replace(route.href as Route); + }, + [router] + ); + + 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/pages/auth/signin/ui/SigninForm.tsx b/src/pages/auth/signin/ui/SigninForm.tsx index 6fa91e4..72491be 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 { 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/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..39f041f 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, 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}
From c4783f12c5640a04326a203d5a120dfe62f8b836 Mon Sep 17 00:00:00 2001 From: kapitulin24 Date: Fri, 19 Jun 2026 19:32:21 +0300 Subject: [PATCH 4/4] refactor: clean up OAuthLoginButtonsContent and improve redirect logic --- app/(auth)/oauth/route.ts | 3 ++- src/features/auth/oauth-login/model/types.ts | 2 +- .../ui/OAuthLoginButtonsContent.tsx | 18 ++++++------------ src/features/auth/sign-out/model/useSignOut.ts | 2 +- src/features/otp-form/model/useResend.ts | 2 +- src/features/otp-form/ui/ResendCodeControl.tsx | 2 +- .../teams/active-team/model/useSwitchTeam.ts | 2 +- .../teams/invite/model/useInviteTeamMember.ts | 8 ++++---- .../upload-avatar/model/useUploadAvatar.ts | 2 +- src/features/upload-avatar/ui/UploadAvatar.tsx | 2 +- .../forgot-password/model/useResetPassword.ts | 2 +- .../auth/forgot-password/model/useSendCode.ts | 2 +- .../forgot-password/model/useSendPassword.ts | 2 +- .../auth/forgot-password/ui/EmailForm.tsx | 2 +- .../auth/forgot-password/ui/PasswordForm.tsx | 2 +- src/pages/auth/signin/model/useSignin.ts | 2 +- src/pages/auth/signin/ui/SigninForm.tsx | 2 +- src/pages/auth/signup/model/useSignup.ts | 2 +- .../auth/signup/model/useSignupConfirm.ts | 2 +- .../signup/model/utils/field-name-mapper.ts | 2 +- src/pages/auth/signup/ui/SignupForm.tsx | 2 +- src/pages/invitations/ui/InvitationCard.tsx | 2 +- .../profile/api/useUpdateNotifications.ts | 2 +- src/pages/profile/model/notifications.ts | 2 +- src/pages/profile/model/useMePage.ts | 2 +- src/pages/profile/ui/me-page/IdentityItem.tsx | 2 +- .../account-section/OAuthManageButton.tsx | 2 +- src/pages/team/api/useUploadCover.ts | 2 +- src/pages/team/config/member.ts | 2 +- src/pages/team/model/useMembersPage.ts | 2 +- .../team/ui/invitations/InvitationCard.tsx | 2 +- .../ui/invitations/InvitationRoleSelect.tsx | 2 +- src/pages/team/ui/members/MemberCard.tsx | 2 +- src/pages/team/ui/members/MemberRoleSelect.tsx | 2 +- .../team/ui/members/MemberStatusSelect.tsx | 2 +- src/pages/team/ui/projects/ProjectCard.tsx | 2 +- src/pages/team/ui/settings/SaveBar.tsx | 2 +- src/pages/team/ui/settings/TeamIdentity.tsx | 2 +- src/pages/teams/ui/TeamCard.tsx | 2 +- src/pages/teams/ui/TeamCardActions.tsx | 2 +- .../app-sidebar/ui/projects/ProjectActions.tsx | 2 +- .../ui/projects/ProjectsContent.tsx | 13 ++++++------- .../app-sidebar/ui/teams/TeamTrigger.tsx | 2 +- 43 files changed, 57 insertions(+), 63 deletions(-) diff --git a/app/(auth)/oauth/route.ts b/app/(auth)/oauth/route.ts index 8d1d19f..9e3a7a1 100644 --- a/app/(auth)/oauth/route.ts +++ b/app/(auth)/oauth/route.ts @@ -64,8 +64,9 @@ export async function GET(request: NextRequest) { successUrl.searchParams.set('message', 'Операция выполнена успешно'); const res = NextResponse.redirect(successUrl); + const cookies = response.headers.getSetCookie() ?? []; - (response.headers.getSetCookie() ?? []).forEach((cookie) => { + cookies.forEach((cookie) => { res.headers.append('Set-Cookie', cookie); }); 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/OAuthLoginButtonsContent.tsx b/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx index 83e201e..d0d1e38 100644 --- a/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx @@ -2,9 +2,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'; import { AuthQueries, OAUTH_PROVIDERS, type TAuth } from 'entities/auth'; -import { Route } from 'next'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; import { useCallback } from 'react'; import { routes } from 'shared/config'; import { cn } from 'shared/lib/utils'; @@ -16,19 +14,15 @@ export interface OAuthLoginButtonsContentProps { export function OAuthLoginButtonsContent({ className }: OAuthLoginButtonsContentProps) { const { data } = useSuspenseQuery(AuthQueries.getOAuthProviders()); - const router = useRouter(); - const startOAuth = useCallback( - (provider: TAuth.OAuthProvider) => { - const route = new URL(routes.auth.oauth(), window?.location.origin); + 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'); + route.searchParams.set('provider', provider); + route.searchParams.set('startOAuth', 'true'); - router.replace(route.href as Route); - }, - [router] - ); + window.location.assign(route.href); + }, []); return (
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/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 72491be..ad65507 100644 --- a/src/pages/auth/signin/ui/SigninForm.tsx +++ b/src/pages/auth/signin/ui/SigninForm.tsx @@ -1,7 +1,7 @@ 'use client'; import { zodResolver } from '@hookform/resolvers/zod'; -import { TAuth } from 'entities/auth'; +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'; 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/OAuthManageButton.tsx b/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx index 39f041f..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,6 +1,6 @@ 'use client'; -import { authFabricKeys, OAUTH_PROVIDERS, TAuth } from 'entities/auth'; +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'; 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 be85f17..afb78b2 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 63c09b7..daf6893 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 9808d45..9706164 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';