From d7195a0f57d4a5c388fb3e29cab419872679fce0 Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Fri, 12 Jun 2026 22:03:39 +0300 Subject: [PATCH 1/6] refactor(auth): move reusable variables and components to entities, and move icons to shared --- src/entities/auth/index.ts | 2 + src/entities/auth/model/const.ts | 26 +++++++++ src/entities/auth/ui/OAuthButton.tsx | 30 ++++++++++ src/features/auth/oauth-login/model/consts.ts | 39 ------------- .../auth/oauth-login/ui/OAuthIcons.tsx | 56 ------------------- .../auth/oauth-login/ui/OAuthLoginButtons.tsx | 36 ++++++------ src/shared/ui/icon/auth/GithubIcon.tsx | 12 ++++ src/shared/ui/icon/auth/GoogleIcon.tsx | 25 +++++++++ src/shared/ui/icon/auth/VkontakteIcon.tsx | 12 ++++ src/shared/ui/icon/auth/YandexIcon.tsx | 12 ++++ src/shared/ui/icon/index.ts | 5 ++ src/shared/ui/icon/types.ts | 1 + src/shared/ui/index.ts | 1 + 13 files changed, 142 insertions(+), 115 deletions(-) create mode 100644 src/entities/auth/ui/OAuthButton.tsx delete mode 100644 src/features/auth/oauth-login/model/consts.ts delete mode 100644 src/features/auth/oauth-login/ui/OAuthIcons.tsx create mode 100644 src/shared/ui/icon/auth/GithubIcon.tsx create mode 100644 src/shared/ui/icon/auth/GoogleIcon.tsx create mode 100644 src/shared/ui/icon/auth/VkontakteIcon.tsx create mode 100644 src/shared/ui/icon/auth/YandexIcon.tsx create mode 100644 src/shared/ui/icon/index.ts create mode 100644 src/shared/ui/icon/types.ts diff --git a/src/entities/auth/index.ts b/src/entities/auth/index.ts index 5287e31..2c2befa 100644 --- a/src/entities/auth/index.ts +++ b/src/entities/auth/index.ts @@ -3,3 +3,5 @@ export type * as TAuth from './model/types'; export * as CAuth from './model/const'; export { AuthHttp } from './api/http'; export { AuthQueries } from './api/queries'; +export { type OAuthProviderMeta } from './model/const'; +export { OAuthButton } from './ui/OAuthButton'; diff --git a/src/entities/auth/model/const.ts b/src/entities/auth/model/const.ts index 2d40b1f..469404e 100644 --- a/src/entities/auth/model/const.ts +++ b/src/entities/auth/model/const.ts @@ -1,6 +1,32 @@ +import { type TAuth } from 'entities/auth'; +import { ComponentType, SVGProps } from 'react'; +import { GithubIcon, GoogleIcon, VkontakteIcon, YandexIcon } from 'shared/ui'; + export const MIN_PASS_LENGTH = 8; export const MAX_PASS_LENGTH = 32; export const OTP_LENGTH = 6; export const MIN_NAME_LENGTH = 2; export const MAX_NAME_LENGTH = 50; + +export type OAuthProviderMeta = { + label: string; + icon: ComponentType>; + buttonClassName?: string; +}; + +export const OAUTH_PROVIDERS: Record = { + yandex: { label: 'Яндекс', icon: YandexIcon }, + vkontakte: { + label: 'Вконтакте', + icon: VkontakteIcon, + buttonClassName: 'bg-[#07f] hover:bg-[#07f]', + }, + google: { label: 'Google', icon: GoogleIcon }, + github: { + label: 'GitHub', + icon: GithubIcon, + buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] hover:opacity-80', + }, +}; +export const OAUTH_PROVIDERS_COUNT = Object.keys(OAUTH_PROVIDERS).length; diff --git a/src/entities/auth/ui/OAuthButton.tsx b/src/entities/auth/ui/OAuthButton.tsx new file mode 100644 index 0000000..753548a --- /dev/null +++ b/src/entities/auth/ui/OAuthButton.tsx @@ -0,0 +1,30 @@ +import { ButtonHTMLAttributes } from 'react'; +import { Button } from 'shared/ui'; // или откуда у тебя берется базовый Button +import { cn } from 'shared/lib/utils'; +import { OAUTH_PROVIDERS } from '../model/const'; +import type * as TAuth from '../model/types'; + +type OAuthButtonProps = ButtonHTMLAttributes & { + provider: TAuth.OAuthProvider; + iconClassName?: string; +}; + +export function OAuthButton({ provider, className, iconClassName, ...props }: OAuthButtonProps) { + const meta = OAUTH_PROVIDERS[provider]; + + if (!meta) return null; + + const Icon = meta.icon; + + return ( + + ); +} diff --git a/src/features/auth/oauth-login/model/consts.ts b/src/features/auth/oauth-login/model/consts.ts deleted file mode 100644 index d611103..0000000 --- a/src/features/auth/oauth-login/model/consts.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type TAuth } from 'entities/auth'; -import { ComponentType, SVGProps } from 'react'; -import { GithubIcon, GoogleIcon, VkontakteIcon, YandexIcon } from '../ui/OAuthIcons'; -import { routes } from 'shared/config'; -import { StartOauthParams } from './types'; - -const getRoute = (provider: TAuth.OAuthProvider) => { - const params = new URLSearchParams({ - provider, - startOAuth: 'true', - } satisfies Record); - - return `${routes.auth.oauth()}?${params.toString()}`; -}; - -export type OAuthProviderConfig = { - label: string; - icon: ComponentType>; - href: string; - color?: string; -}; - -export const OAUTH_PROVIDERS: Record = { - yandex: { label: 'Яндекс', icon: YandexIcon, href: getRoute('yandex') }, - vkontakte: { - label: 'Вконтакте', - icon: VkontakteIcon, - href: getRoute('vkontakte'), - color: '#07f', - }, - google: { label: 'Google', icon: GoogleIcon, href: getRoute('google') }, - github: { - label: 'GitHub', - icon: GithubIcon, - href: getRoute('github'), - color: '#24292f', - }, -}; -export const OAUTH_PROVIDERS_COUNT = Object.keys(OAUTH_PROVIDERS).length; diff --git a/src/features/auth/oauth-login/ui/OAuthIcons.tsx b/src/features/auth/oauth-login/ui/OAuthIcons.tsx deleted file mode 100644 index 9332584..0000000 --- a/src/features/auth/oauth-login/ui/OAuthIcons.tsx +++ /dev/null @@ -1,56 +0,0 @@ -type IconProps = { className?: string }; - -export function GoogleIcon({ className }: IconProps) { - return ( - - - - - - - - ); -} - -export function YandexIcon({ className }: IconProps) { - return ( - - - - ); -} -export function GithubIcon({ className }: IconProps) { - return ( - - - - ); -} -export function VkontakteIcon({ className }: IconProps) { - return ( - - - - ); -} diff --git a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx index 9de4cd7..99e5d52 100644 --- a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx @@ -1,11 +1,21 @@ 'use client'; -import { Button, Skeleton } from 'shared/ui'; -import { OAUTH_PROVIDERS, OAUTH_PROVIDERS_COUNT } from '../model/consts'; +import { Skeleton } from 'shared/ui'; +import { CAuth, OAuthButton } from 'entities/auth'; import { cn } from 'shared/lib/utils'; -import Link from 'next/link'; import { useQuery } from '@tanstack/react-query'; import { AuthQueries } from 'entities/auth'; -import { type Route } from 'next'; +import { type TAuth } from 'entities/auth'; +import { routes } from 'shared/config'; +import { StartOauthParams } from '../model/types'; + +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()); @@ -13,26 +23,12 @@ export function OAuthLoginButtons({ className }: { className?: string }) { return (
{isLoading && - Array.from({ length: OAUTH_PROVIDERS_COUNT }, (_v, i) => ( + Array.from({ length: CAuth.OAUTH_PROVIDERS_COUNT }, (_v, i) => ( ))} {data?.map((item) => { - const providerConfig = OAUTH_PROVIDERS[item.value]; - - return ( - - ); + return ; })}
); diff --git a/src/shared/ui/icon/auth/GithubIcon.tsx b/src/shared/ui/icon/auth/GithubIcon.tsx new file mode 100644 index 0000000..991dff8 --- /dev/null +++ b/src/shared/ui/icon/auth/GithubIcon.tsx @@ -0,0 +1,12 @@ +import { IconProps } from '../types'; + +export function GithubIcon({ className }: IconProps) { + return ( + + + + ); +} diff --git a/src/shared/ui/icon/auth/GoogleIcon.tsx b/src/shared/ui/icon/auth/GoogleIcon.tsx new file mode 100644 index 0000000..c1c7276 --- /dev/null +++ b/src/shared/ui/icon/auth/GoogleIcon.tsx @@ -0,0 +1,25 @@ +import { IconProps } from '../types'; + +export function GoogleIcon({ className }: IconProps) { + return ( + + + + + + + + ); +} diff --git a/src/shared/ui/icon/auth/VkontakteIcon.tsx b/src/shared/ui/icon/auth/VkontakteIcon.tsx new file mode 100644 index 0000000..0bba3c3 --- /dev/null +++ b/src/shared/ui/icon/auth/VkontakteIcon.tsx @@ -0,0 +1,12 @@ +import { IconProps } from '../types'; + +export function VkontakteIcon({ className }: IconProps) { + return ( + + + + ); +} diff --git a/src/shared/ui/icon/auth/YandexIcon.tsx b/src/shared/ui/icon/auth/YandexIcon.tsx new file mode 100644 index 0000000..6bb9df4 --- /dev/null +++ b/src/shared/ui/icon/auth/YandexIcon.tsx @@ -0,0 +1,12 @@ +import { IconProps } from '../types'; + +export function YandexIcon({ className }: IconProps) { + return ( + + + + ); +} diff --git a/src/shared/ui/icon/index.ts b/src/shared/ui/icon/index.ts new file mode 100644 index 0000000..54097b6 --- /dev/null +++ b/src/shared/ui/icon/index.ts @@ -0,0 +1,5 @@ +export { type IconProps } from './types'; +export { GithubIcon } from './auth/GithubIcon'; +export { GoogleIcon } from './auth/GoogleIcon'; +export { VkontakteIcon } from './auth/VkontakteIcon'; +export { YandexIcon } from './auth/YandexIcon'; diff --git a/src/shared/ui/icon/types.ts b/src/shared/ui/icon/types.ts new file mode 100644 index 0000000..fae7fa0 --- /dev/null +++ b/src/shared/ui/icon/types.ts @@ -0,0 +1 @@ +export type IconProps = { className?: string }; diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index 8a9ae79..df97fc1 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -39,3 +39,4 @@ export * from './Select'; export * from './Empty'; export * from './ScrollArea'; export * from './Kanban'; +export * from './icon'; From 833e62a60e7669b140c4983098cb53a7e1c7c1e3 Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Sat, 13 Jun 2026 01:53:17 +0300 Subject: [PATCH 2/6] refactor: remove logic duplication for redirect, add QueryParamshandler.tsx --- .../signin/model/useAuthRedirectMessage.ts | 19 ----------- .../auth/signin/ui/AuthRedirectHandler.tsx | 7 ---- src/pages/auth/signin/ui/SigninPage.tsx | 12 ++++--- src/pages/profile/ui/me-page/MePage.tsx | 13 ++++++-- src/shared/ui/QueryParamsHandler.tsx | 32 +++++++++++++++++++ src/shared/ui/icon/auth/YandexIcon.tsx | 2 +- src/shared/ui/index.ts | 1 + 7 files changed, 52 insertions(+), 34 deletions(-) delete mode 100644 src/pages/auth/signin/model/useAuthRedirectMessage.ts delete mode 100644 src/pages/auth/signin/ui/AuthRedirectHandler.tsx create mode 100644 src/shared/ui/QueryParamsHandler.tsx diff --git a/src/pages/auth/signin/model/useAuthRedirectMessage.ts b/src/pages/auth/signin/model/useAuthRedirectMessage.ts deleted file mode 100644 index 8d64438..0000000 --- a/src/pages/auth/signin/model/useAuthRedirectMessage.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useEffect } from 'react'; -import { routes } from 'shared/config'; -import { toast } from 'sonner'; - -export function useAuthRedirectMessage() { - const params = useSearchParams(); - const router = useRouter(); - - useEffect(() => { - const error = params?.get('oauth_error'); - const message = params?.get('message'); - - if (!error) return; - toast.error(message ?? 'Authorization failed'); - router.replace(routes.auth.signin()); - }, [params, router]); -} diff --git a/src/pages/auth/signin/ui/AuthRedirectHandler.tsx b/src/pages/auth/signin/ui/AuthRedirectHandler.tsx deleted file mode 100644 index 2b5a8ee..0000000 --- a/src/pages/auth/signin/ui/AuthRedirectHandler.tsx +++ /dev/null @@ -1,7 +0,0 @@ -'use client'; -import { useAuthRedirectMessage } from '../model/useAuthRedirectMessage'; - -export function AuthRedirectHandler() { - useAuthRedirectMessage(); - return null; -} diff --git a/src/pages/auth/signin/ui/SigninPage.tsx b/src/pages/auth/signin/ui/SigninPage.tsx index 70cbad5..6711c51 100644 --- a/src/pages/auth/signin/ui/SigninPage.tsx +++ b/src/pages/auth/signin/ui/SigninPage.tsx @@ -6,16 +6,18 @@ import { routes } from 'shared/config'; import { AccessToken } from 'shared/api'; import { toast } from 'sonner'; import { useRouter } from 'next/navigation'; -import { Suspense } from 'react'; -import { AuthRedirectHandler } from './AuthRedirectHandler'; +import dynamic from 'next/dynamic'; + +const QueryParamsHandler = dynamic( + () => import('shared/ui').then((mod) => mod.QueryParamsHandler), + { ssr: false } +); function SigninPage() { const router = useRouter(); return ( <> - - - +
diff --git a/src/pages/profile/ui/me-page/MePage.tsx b/src/pages/profile/ui/me-page/MePage.tsx index 838e1ef..a7beb01 100644 --- a/src/pages/profile/ui/me-page/MePage.tsx +++ b/src/pages/profile/ui/me-page/MePage.tsx @@ -12,6 +12,13 @@ import { import { IdentityItem } from './IdentityItem'; import { ProfileForm } from './ProfileForm'; import { useMePage } from '../../model/useMePage'; +import { AccountSection } from './AccountsSection'; +import dynamic from 'next/dynamic'; + +const QueryParamsHandler = dynamic( + () => import('shared/ui').then((mod) => mod.QueryParamsHandler), + { ssr: false } +); function MePage() { const { form, profile, email, isDirty, isPending, onSubmit, onDiscard } = useMePage(); @@ -28,7 +35,8 @@ function MePage() { } return ( - <> +
+ + - +
); } diff --git a/src/shared/ui/QueryParamsHandler.tsx b/src/shared/ui/QueryParamsHandler.tsx new file mode 100644 index 0000000..96ee8c2 --- /dev/null +++ b/src/shared/ui/QueryParamsHandler.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { type Route } from 'next'; +import { useRouter } from 'next/navigation'; +import { useSearchParams } from 'next/navigation'; +import { useEffect, useRef } from 'react'; +import { toast } from 'sonner'; + +function useQueryParams() { + const params = useSearchParams(); + const isShowToast = useRef(false); + const router = useRouter(); + useEffect(() => { + const success = params?.get('success'); + const message = params?.get('message'); + + if (isShowToast.current || !success) return; + + if (success === 'true' && message) { + toast.success(message); + } else if (success === 'false' && message) { + toast.error(message); + } + isShowToast.current = true; + router.replace(location.pathname as Route); + }, [params, router]); +} + +export function QueryParamsHandler() { + useQueryParams(); + return null; +} diff --git a/src/shared/ui/icon/auth/YandexIcon.tsx b/src/shared/ui/icon/auth/YandexIcon.tsx index 6bb9df4..8b61981 100644 --- a/src/shared/ui/icon/auth/YandexIcon.tsx +++ b/src/shared/ui/icon/auth/YandexIcon.tsx @@ -4,7 +4,7 @@ export function YandexIcon({ className }: IconProps) { return ( diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts index df97fc1..c91b40a 100644 --- a/src/shared/ui/index.ts +++ b/src/shared/ui/index.ts @@ -40,3 +40,4 @@ export * from './Empty'; export * from './ScrollArea'; export * from './Kanban'; export * from './icon'; +export { QueryParamsHandler } from './QueryParamsHandler'; From 76f800276578db8733766614f770164cceb7891d Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Sat, 13 Jun 2026 01:58:20 +0300 Subject: [PATCH 3/6] feature(profile): add redirect page, improve oauth redirect page by adding success message, add signal in http, add authKeys, add oauth config classNames --- app/(protected)/profile/page.tsx | 1 + src/entities/auth/api/http.ts | 5 ++-- src/entities/auth/model/const.ts | 14 ++++++++-- src/pages/auth/oauth/ui/OAuthPage.tsx | 8 ++++-- src/pages/profile/index.ts | 1 + .../profile/ui/profile-page/ProfilePage.tsx | 28 +++++++++++++++++++ src/shared/config/routes.ts | 1 + 7 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 app/(protected)/profile/page.tsx create mode 100644 src/pages/profile/ui/profile-page/ProfilePage.tsx diff --git a/app/(protected)/profile/page.tsx b/app/(protected)/profile/page.tsx new file mode 100644 index 0000000..d071d18 --- /dev/null +++ b/app/(protected)/profile/page.tsx @@ -0,0 +1 @@ +export { ProfilePage as default } from 'pages/profile/'; diff --git a/src/entities/auth/api/http.ts b/src/entities/auth/api/http.ts index 3b5a245..959f5a1 100644 --- a/src/entities/auth/api/http.ts +++ b/src/entities/auth/api/http.ts @@ -96,16 +96,17 @@ export class AuthHttp { signal, }); } - static connectedOAuthProviders() { + static connectedOAuthProviders(signal: AbortSignal) { return api({ url: '/auth/oauth/providers/connected', method: 'GET', contracts: { response: SAuth.ConnectedOAuthProvidersResponse, }, + signal, }); } - static connecteOAuthProvder(provider: TAuth.OAuthProvider) { + static connectOAuthProvder(provider: TAuth.OAuthProvider) { return api({ url: `/auth/oauth/${provider}/connect`, method: 'POST', diff --git a/src/entities/auth/model/const.ts b/src/entities/auth/model/const.ts index 469404e..3e954a5 100644 --- a/src/entities/auth/model/const.ts +++ b/src/entities/auth/model/const.ts @@ -16,7 +16,11 @@ export type OAuthProviderMeta = { }; export const OAUTH_PROVIDERS: Record = { - yandex: { label: 'Яндекс', icon: YandexIcon }, + yandex: { + label: 'Яндекс', + icon: YandexIcon, + buttonClassName: 'text-[#fc3f1d] hover:text-[#fc3f1d]', + }, vkontakte: { label: 'Вконтакте', icon: VkontakteIcon, @@ -26,7 +30,13 @@ export const OAUTH_PROVIDERS: Record = { github: { label: 'GitHub', icon: GithubIcon, - buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] hover:opacity-80', + buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] ', }, }; export const OAUTH_PROVIDERS_COUNT = Object.keys(OAUTH_PROVIDERS).length; + +export const authKeys = { + all: ['auth'] as const, + availableProviders: () => [...authKeys.all, 'providers', 'available'] as const, + connectedProviders: () => [...authKeys.all, 'providers', 'connected'] as const, +}; diff --git a/src/pages/auth/oauth/ui/OAuthPage.tsx b/src/pages/auth/oauth/ui/OAuthPage.tsx index 960533b..16613be 100644 --- a/src/pages/auth/oauth/ui/OAuthPage.tsx +++ b/src/pages/auth/oauth/ui/OAuthPage.tsx @@ -21,6 +21,8 @@ interface Props { 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); } @@ -30,11 +32,13 @@ export async function OAuthPage({ searchParams }: Props) { } if (success === 'true') { - redirect(routes.user.profile()); + redirect( + `${routes.user.profile()}?success=true&message=${encodeURIComponent(message || 'Вход успешен')}` + ); } const errorUrl = message - ? `${routes.auth.signin()}?oauth_error=1&message=${encodeURIComponent(message)}` + ? `${routes.auth.signin()}?success=true&message=${encodeURIComponent(message)}` : routes.auth.signin(); redirect(errorUrl as Route); diff --git a/src/pages/profile/index.ts b/src/pages/profile/index.ts index 8922138..76b7a15 100644 --- a/src/pages/profile/index.ts +++ b/src/pages/profile/index.ts @@ -2,3 +2,4 @@ export { profileTabs } from './config/tabs'; export { MePage } from './ui/me-page/MePage'; export { NotificationsPage } from './ui/notifications-page/NotificationsPage'; export { SecurityPage } from './ui/security-page/SecurityPage'; +export { ProfilePage } from './ui/profile-page/ProfilePage'; diff --git a/src/pages/profile/ui/profile-page/ProfilePage.tsx b/src/pages/profile/ui/profile-page/ProfilePage.tsx new file mode 100644 index 0000000..cf35eaa --- /dev/null +++ b/src/pages/profile/ui/profile-page/ProfilePage.tsx @@ -0,0 +1,28 @@ +import { type Route } from 'next'; +import { redirect } from 'next/navigation'; +import { routes } from 'shared/config'; + +type BooleanRaw = 'false' | 'true'; +type PageParams = { + success: BooleanRaw; + message: string; +}; + +interface Props { + searchParams: Promise>; +} +export async function ProfilePage({ searchParams }: Props) { + const { success, message } = await searchParams; + + if (success === 'true') { + redirect( + `${routes.user.profile()}?success=true&message=${encodeURIComponent(message || 'Провайдер успешно привязан')}` + ); + } + + const errorUrl = message + ? `${routes.user.profile()}?success=false&message=${encodeURIComponent(message)}` + : routes.user.profile(); + + redirect(errorUrl as Route); +} diff --git a/src/shared/config/routes.ts b/src/shared/config/routes.ts index 36081e4..d226fea 100644 --- a/src/shared/config/routes.ts +++ b/src/shared/config/routes.ts @@ -2,6 +2,7 @@ import type { Route } from 'next'; export const routes = { home: (): Route => '/', + profile: (): Route => '/profile', user: { root: (): Route => '/user', profile: (): Route => '/user/profile', From d9ab38d7f4b7dfaf8eec58397a67ef0267a22cd3 Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Sat, 13 Jun 2026 01:59:37 +0300 Subject: [PATCH 4/6] feature(profile): add connect and disconnect OAuth provider feature on profile page --- src/entities/auth/api/queries.ts | 10 ++- src/entities/auth/index.ts | 1 - src/entities/auth/ui/OAuthButton.tsx | 30 --------- .../auth/oauth-login/ui/OAuthButton.tsx | 45 +++++++++++++ .../auth/oauth-login/ui/OAuthLoginButtons.tsx | 12 +++- src/pages/auth/signup/ui/SignupForm.tsx | 3 + .../profile/model/useConnectOauthProvider.ts | 21 ++++++ .../profile/model/useConnectedAccounts.ts | 23 +++++++ .../model/useDisconnectOauthProvider.ts | 19 ++++++ src/pages/profile/ui/me-page/MePage.tsx | 2 +- .../account-section/AccountsSection.tsx | 26 ++++++++ .../account-section/OAuthManageButton.tsx | 64 +++++++++++++++++++ 12 files changed, 221 insertions(+), 35 deletions(-) delete mode 100644 src/entities/auth/ui/OAuthButton.tsx create mode 100644 src/features/auth/oauth-login/ui/OAuthButton.tsx create mode 100644 src/pages/profile/model/useConnectOauthProvider.ts create mode 100644 src/pages/profile/model/useConnectedAccounts.ts create mode 100644 src/pages/profile/model/useDisconnectOauthProvider.ts create mode 100644 src/pages/profile/ui/me-page/account-section/AccountsSection.tsx create mode 100644 src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx diff --git a/src/entities/auth/api/queries.ts b/src/entities/auth/api/queries.ts index f1c3a2c..5ade73e 100644 --- a/src/entities/auth/api/queries.ts +++ b/src/entities/auth/api/queries.ts @@ -1,12 +1,20 @@ import { queryOptions } from '@tanstack/react-query'; import { AuthHttp } from './http'; +import { authKeys } from '../model/const'; export class AuthQueries { static getOAuthProviders() { return queryOptions({ - queryKey: ['oauth-providers'], + queryKey: authKeys.availableProviders(), queryFn: async ({ signal }) => AuthHttp.oAuthProviders(signal), staleTime: 60_000 * 360 * 24, }); } + static getConnectedOAuthProviders() { + return queryOptions({ + queryKey: authKeys.connectedProviders(), + queryFn: async ({ signal }) => AuthHttp.connectedOAuthProviders(signal), + staleTime: 60_000 * 360 * 24, + }); + } } diff --git a/src/entities/auth/index.ts b/src/entities/auth/index.ts index 2c2befa..146ba7e 100644 --- a/src/entities/auth/index.ts +++ b/src/entities/auth/index.ts @@ -4,4 +4,3 @@ export * as CAuth from './model/const'; export { AuthHttp } from './api/http'; export { AuthQueries } from './api/queries'; export { type OAuthProviderMeta } from './model/const'; -export { OAuthButton } from './ui/OAuthButton'; diff --git a/src/entities/auth/ui/OAuthButton.tsx b/src/entities/auth/ui/OAuthButton.tsx deleted file mode 100644 index 753548a..0000000 --- a/src/entities/auth/ui/OAuthButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { ButtonHTMLAttributes } from 'react'; -import { Button } from 'shared/ui'; // или откуда у тебя берется базовый Button -import { cn } from 'shared/lib/utils'; -import { OAUTH_PROVIDERS } from '../model/const'; -import type * as TAuth from '../model/types'; - -type OAuthButtonProps = ButtonHTMLAttributes & { - provider: TAuth.OAuthProvider; - iconClassName?: string; -}; - -export function OAuthButton({ provider, className, iconClassName, ...props }: OAuthButtonProps) { - const meta = OAUTH_PROVIDERS[provider]; - - if (!meta) return null; - - const Icon = meta.icon; - - return ( - - ); -} diff --git a/src/features/auth/oauth-login/ui/OAuthButton.tsx b/src/features/auth/oauth-login/ui/OAuthButton.tsx new file mode 100644 index 0000000..7be23fd --- /dev/null +++ b/src/features/auth/oauth-login/ui/OAuthButton.tsx @@ -0,0 +1,45 @@ +import type { ButtonHTMLAttributes } from 'react'; +import { Button, buttonVariants } from 'shared/ui'; +import { cn } from 'shared/lib/utils'; +import { CAuth } from 'entities/auth'; +import type { TAuth } from 'entities/auth'; +import { type VariantProps } from 'class-variance-authority'; +import Link from 'next/link'; +import { type Route } from 'next'; + +type OAuthButtonProps = Omit, 'children'> & + VariantProps & { + provider: TAuth.OAuthProvider; + iconClassName?: string; + href: Route; + }; + +export function OAuthButton({ + provider, + className, + iconClassName, + size = 'icon', + variant = 'outline', + href, + ...props +}: OAuthButtonProps) { + const meta = CAuth.OAUTH_PROVIDERS[provider]; + + if (!meta) return null; + + const Icon = meta.icon; + + return ( + + ); +} diff --git a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx index 99e5d52..2ba8d5c 100644 --- a/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx +++ b/src/features/auth/oauth-login/ui/OAuthLoginButtons.tsx @@ -1,12 +1,14 @@ 'use client'; import { Skeleton } from 'shared/ui'; -import { CAuth, OAuthButton } from 'entities/auth'; +import { CAuth } from 'entities/auth'; import { cn } from 'shared/lib/utils'; import { useQuery } from '@tanstack/react-query'; import { AuthQueries } from 'entities/auth'; import { type TAuth } from 'entities/auth'; import { routes } from 'shared/config'; import { StartOauthParams } from '../model/types'; +import { OAuthButton } from './OAuthButton'; +import { type Route } from 'next'; export const getRoute = (provider: TAuth.OAuthProvider) => { const params = new URLSearchParams({ @@ -28,7 +30,13 @@ export function OAuthLoginButtons({ className }: { className?: string }) { ))} {data?.map((item) => { - return ; + return ( + + ); })}
); diff --git a/src/pages/auth/signup/ui/SignupForm.tsx b/src/pages/auth/signup/ui/SignupForm.tsx index 7d7b61b..dc74434 100644 --- a/src/pages/auth/signup/ui/SignupForm.tsx +++ b/src/pages/auth/signup/ui/SignupForm.tsx @@ -30,6 +30,7 @@ import { prepareFullName } from '../model/utils/prepare-fullname'; import { extractValidationIssues } from 'shared/api'; import { TAuth } from 'entities/auth'; import { useSignup, type UseSignupOptions } from '../model/useSignup'; +import { OAuthLoginButtons, OAuthSeparator } from 'features/auth/oauth-login'; interface SignupFormProps extends Omit, 'children' | 'onSubmit'> { mutateOptions?: UseSignupOptions; @@ -183,6 +184,8 @@ export function SignupForm({ className, mutateOptions = {}, ...props }: SignupFo + + ); diff --git a/src/pages/profile/model/useConnectOauthProvider.ts b/src/pages/profile/model/useConnectOauthProvider.ts new file mode 100644 index 0000000..a4032bf --- /dev/null +++ b/src/pages/profile/model/useConnectOauthProvider.ts @@ -0,0 +1,21 @@ +import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; +import { AuthHttp, TAuth } from 'entities/auth'; + +export type UseConnectOAuthProviderOptions = Omit< + UseMutationOptions, + 'mutationFn' +>; + +export function useConnectOAuthProvider({ ...rest }: UseConnectOAuthProviderOptions = {}) { + return useMutation< + Awaited, + DefaultError, + TAuth.OAuthProvider + >({ + ...rest, + mutationFn: AuthHttp.connectOAuthProvder, + meta: { + skipGlobalValidationToast: true, + }, + }); +} diff --git a/src/pages/profile/model/useConnectedAccounts.ts b/src/pages/profile/model/useConnectedAccounts.ts new file mode 100644 index 0000000..98c002c --- /dev/null +++ b/src/pages/profile/model/useConnectedAccounts.ts @@ -0,0 +1,23 @@ +'use client'; +import { useQuery } from '@tanstack/react-query'; +import { AuthQueries } from 'entities/auth'; +import { useMemo } from 'react'; + +export function useConnectedAccounts() { + // провайдеры + const available = useQuery(AuthQueries.getOAuthProviders()); + const connected = useQuery(AuthQueries.getConnectedOAuthProviders()); + + const providers = useMemo(() => { + if (!available.data) return []; + + const connectedSet = new Set(connected.data?.map((v) => v.provider)); + + return available.data.map((item) => ({ + ...item, + isConnected: connectedSet.has(item.value), + })); + }, [available.data, connected.data]); + + return { providers }; +} diff --git a/src/pages/profile/model/useDisconnectOauthProvider.ts b/src/pages/profile/model/useDisconnectOauthProvider.ts new file mode 100644 index 0000000..2f6bec0 --- /dev/null +++ b/src/pages/profile/model/useDisconnectOauthProvider.ts @@ -0,0 +1,19 @@ +import { type DefaultError, useMutation, UseMutationOptions } from '@tanstack/react-query'; +import { AuthHttp, TAuth } from 'entities/auth'; + +export type UseDisconnectOAuthProviderOptions = Omit< + UseMutationOptions, + 'mutationFn' +>; + +export function useDisconnectOAuthProvider({ ...rest }: UseDisconnectOAuthProviderOptions = {}) { + return useMutation, DefaultError, TAuth.OAuthProvider>( + { + ...rest, + mutationFn: AuthHttp.removeOAuthProvder, + meta: { + skipGlobalValidationToast: true, + }, + } + ); +} diff --git a/src/pages/profile/ui/me-page/MePage.tsx b/src/pages/profile/ui/me-page/MePage.tsx index a7beb01..93f35e6 100644 --- a/src/pages/profile/ui/me-page/MePage.tsx +++ b/src/pages/profile/ui/me-page/MePage.tsx @@ -12,7 +12,7 @@ import { import { IdentityItem } from './IdentityItem'; import { ProfileForm } from './ProfileForm'; import { useMePage } from '../../model/useMePage'; -import { AccountSection } from './AccountsSection'; +import { AccountSection } from './account-section/AccountsSection'; import dynamic from 'next/dynamic'; const QueryParamsHandler = dynamic( diff --git a/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx b/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx new file mode 100644 index 0000000..929cb02 --- /dev/null +++ b/src/pages/profile/ui/me-page/account-section/AccountsSection.tsx @@ -0,0 +1,26 @@ +import { OAuthManageButton } from './OAuthManageButton'; +import { useConnectedAccounts } from 'pages/profile/model/useConnectedAccounts'; +import { CardSection } from 'shared/ui'; + +export function AccountSection() { + const { providers } = useConnectedAccounts(); + + return ( + + {providers?.map((provider) => { + return ( + + ); + })} + + ); +} diff --git a/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx b/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx new file mode 100644 index 0000000..f62c1b2 --- /dev/null +++ b/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { TAuth } from 'entities/auth'; +import { CAuth } from 'entities/auth'; +import { useCallback, type ComponentProps } from 'react'; +import { Button } from 'shared/ui'; +import { useConnectOAuthProvider } from '../../../model/useConnectOauthProvider'; +import { useDisconnectOAuthProvider } from '../../../model/useDisconnectOauthProvider'; +import { env } from 'shared/config'; +import { toast } from 'sonner'; + +type OAuthManageButtonProps = ComponentProps & { + provider: TAuth.OAuthProvider; + label: string; + isLinked: boolean; +}; + +export function OAuthManageButton({ provider, label, isLinked, ...props }: OAuthManageButtonProps) { + const connect = useConnectOAuthProvider(); + const disconnect = useDisconnectOAuthProvider(); + + const isLoading = connect.isPending || disconnect.isPending; + + const handleToggleConnect = useCallback(() => { + if (isLinked) { + disconnect.mutate(provider, { + onSuccess: (data, _v, _m, context) => { + context.client.invalidateQueries({ queryKey: CAuth.authKeys.connectedProviders() }); + toast.success(data.message); + }, + }); + } else { + connect.mutate(provider, { + onSuccess: (data) => { + const url = data.url.startsWith('http') + ? data.url + : new URL(data.url, env.NEXT_PUBLIC_API_BASE_URL).toString(); + window.location.href = url; + }, + }); + } + }, [connect, disconnect, isLinked, provider]); + + const providerConfig = CAuth.OAUTH_PROVIDERS[provider]; + const Icon = providerConfig.icon; + + return ( + + ); +} From b1505a7dfb27b4fb8ec4bdb858c73212d32b4ccf Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Sat, 13 Jun 2026 04:17:48 +0300 Subject: [PATCH 5/6] refactor: move QueryParamshandler in AppProviders --- src/app/providers/AppProviders.tsx | 11 ++++++++--- .../ui => app/providers}/QueryParamsHandler.tsx | 9 ++------- src/pages/auth/signin/ui/SigninPage.tsx | 7 ------- src/pages/profile/ui/me-page/MePage.tsx | 7 ------- src/shared/ui/index.ts | 1 - src/widgets/app-sidebar/ui/AppSidebar.tsx | 2 ++ 6 files changed, 12 insertions(+), 25 deletions(-) rename src/{shared/ui => app/providers}/QueryParamsHandler.tsx (82%) diff --git a/src/app/providers/AppProviders.tsx b/src/app/providers/AppProviders.tsx index 76e167b..a633adf 100644 --- a/src/app/providers/AppProviders.tsx +++ b/src/app/providers/AppProviders.tsx @@ -1,14 +1,19 @@ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, Suspense } from 'react'; import { QueryProvider } from './QueryProvider'; import { Toaster, TooltipProvider } from 'shared/ui'; import { FrontendObservability } from 'shared/config/'; - +import { QueryParamsHandler } from './QueryParamsHandler'; export function AppProviders({ children }: PropsWithChildren) { return ( <> - {children} + + + + + {children} + diff --git a/src/shared/ui/QueryParamsHandler.tsx b/src/app/providers/QueryParamsHandler.tsx similarity index 82% rename from src/shared/ui/QueryParamsHandler.tsx rename to src/app/providers/QueryParamsHandler.tsx index 96ee8c2..720fc51 100644 --- a/src/shared/ui/QueryParamsHandler.tsx +++ b/src/app/providers/QueryParamsHandler.tsx @@ -1,12 +1,11 @@ 'use client'; import { type Route } from 'next'; -import { useRouter } from 'next/navigation'; -import { useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useEffect, useRef } from 'react'; import { toast } from 'sonner'; -function useQueryParams() { +export function QueryParamsHandler() { const params = useSearchParams(); const isShowToast = useRef(false); const router = useRouter(); @@ -24,9 +23,5 @@ function useQueryParams() { isShowToast.current = true; router.replace(location.pathname as Route); }, [params, router]); -} - -export function QueryParamsHandler() { - useQueryParams(); return null; } diff --git a/src/pages/auth/signin/ui/SigninPage.tsx b/src/pages/auth/signin/ui/SigninPage.tsx index 6711c51..fd6c09b 100644 --- a/src/pages/auth/signin/ui/SigninPage.tsx +++ b/src/pages/auth/signin/ui/SigninPage.tsx @@ -6,18 +6,11 @@ import { routes } from 'shared/config'; import { AccessToken } from 'shared/api'; import { toast } from 'sonner'; import { useRouter } from 'next/navigation'; -import dynamic from 'next/dynamic'; - -const QueryParamsHandler = dynamic( - () => import('shared/ui').then((mod) => mod.QueryParamsHandler), - { ssr: false } -); function SigninPage() { const router = useRouter(); return ( <> -
diff --git a/src/pages/profile/ui/me-page/MePage.tsx b/src/pages/profile/ui/me-page/MePage.tsx index 93f35e6..ac4c875 100644 --- a/src/pages/profile/ui/me-page/MePage.tsx +++ b/src/pages/profile/ui/me-page/MePage.tsx @@ -13,12 +13,6 @@ import { IdentityItem } from './IdentityItem'; import { ProfileForm } from './ProfileForm'; import { useMePage } from '../../model/useMePage'; import { AccountSection } from './account-section/AccountsSection'; -import dynamic from 'next/dynamic'; - -const QueryParamsHandler = dynamic( - () => import('shared/ui').then((mod) => mod.QueryParamsHandler), - { ssr: false } -); function MePage() { const { form, profile, email, isDirty, isPending, onSubmit, onDiscard } = useMePage(); @@ -36,7 +30,6 @@ function MePage() { return (
- , 'children'>) { return ( @@ -32,6 +33,7 @@ export function AppSidebar({ ...props }: Omit + тест From 115c8c5f5cd3a3d172cb571796b3486bc17cdf6f Mon Sep 17 00:00:00 2001 From: Alexandr Nelyubov Date: Sat, 13 Jun 2026 17:22:41 +0300 Subject: [PATCH 6/6] refactor(auth): move mutation hooks to API layer, converte auth icons to SVG format, and move them to `public` folder, remove unnecessary props in `OAuthButton`. --- public/github-logo.svg | 6 +++ public/google-logo.svg | 19 ++++++++++ public/vkontakte-logo.svg | 6 +++ public/yandex-logo.svg | 6 +++ src/entities/auth/model/const.ts | 20 +++++----- .../auth/oauth-login/ui/OAuthButton.tsx | 38 ++++++++++--------- .../{model => api}/useConnectOauthProvider.ts | 2 +- .../{model => api}/useConnectedAccounts.ts | 1 - .../useDisconnectOauthProvider.ts | 2 +- .../account-section/AccountsSection.tsx | 2 +- .../account-section/OAuthManageButton.tsx | 12 +++--- 11 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 public/github-logo.svg create mode 100644 public/google-logo.svg create mode 100644 public/vkontakte-logo.svg create mode 100644 public/yandex-logo.svg rename src/pages/profile/{model => api}/useConnectOauthProvider.ts (91%) rename src/pages/profile/{model => api}/useConnectedAccounts.ts (95%) rename src/pages/profile/{model => api}/useDisconnectOauthProvider.ts (91%) diff --git a/public/github-logo.svg b/public/github-logo.svg new file mode 100644 index 0000000..d4c9ed9 --- /dev/null +++ b/public/github-logo.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/public/google-logo.svg b/public/google-logo.svg new file mode 100644 index 0000000..7a6dba8 --- /dev/null +++ b/public/google-logo.svg @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/public/vkontakte-logo.svg b/public/vkontakte-logo.svg new file mode 100644 index 0000000..4f105a1 --- /dev/null +++ b/public/vkontakte-logo.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/public/yandex-logo.svg b/public/yandex-logo.svg new file mode 100644 index 0000000..27e8a20 --- /dev/null +++ b/public/yandex-logo.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/src/entities/auth/model/const.ts b/src/entities/auth/model/const.ts index 3e954a5..0a44380 100644 --- a/src/entities/auth/model/const.ts +++ b/src/entities/auth/model/const.ts @@ -1,6 +1,8 @@ import { type TAuth } from 'entities/auth'; -import { ComponentType, SVGProps } from 'react'; -import { GithubIcon, GoogleIcon, VkontakteIcon, YandexIcon } from 'shared/ui'; +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'; export const MIN_PASS_LENGTH = 8; export const MAX_PASS_LENGTH = 32; @@ -11,28 +13,28 @@ export const MAX_NAME_LENGTH = 50; export type OAuthProviderMeta = { label: string; - icon: ComponentType>; + iconSrc: string; buttonClassName?: string; }; export const OAUTH_PROVIDERS: Record = { yandex: { label: 'Яндекс', - icon: YandexIcon, + iconSrc: YandexIcon, buttonClassName: 'text-[#fc3f1d] hover:text-[#fc3f1d]', }, vkontakte: { label: 'Вконтакте', - icon: VkontakteIcon, + iconSrc: VkontakteIcon, buttonClassName: 'bg-[#07f] hover:bg-[#07f]', }, - google: { label: 'Google', icon: GoogleIcon }, + google: { label: 'Google', iconSrc: GoogleIcon }, github: { label: 'GitHub', - icon: GithubIcon, - buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] ', + iconSrc: GithubIcon, + buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] text-white hover:text-white ', }, -}; +} as const; export const OAUTH_PROVIDERS_COUNT = Object.keys(OAUTH_PROVIDERS).length; export const authKeys = { diff --git a/src/features/auth/oauth-login/ui/OAuthButton.tsx b/src/features/auth/oauth-login/ui/OAuthButton.tsx index 7be23fd..b35d8d0 100644 --- a/src/features/auth/oauth-login/ui/OAuthButton.tsx +++ b/src/features/auth/oauth-login/ui/OAuthButton.tsx @@ -1,44 +1,48 @@ import type { ButtonHTMLAttributes } from 'react'; -import { Button, buttonVariants } from 'shared/ui'; +import { Button } from 'shared/ui'; import { cn } from 'shared/lib/utils'; import { CAuth } from 'entities/auth'; import type { TAuth } from 'entities/auth'; -import { type VariantProps } from 'class-variance-authority'; import Link from 'next/link'; import { type Route } from 'next'; +import Image from 'next/image'; -type OAuthButtonProps = Omit, 'children'> & - VariantProps & { - provider: TAuth.OAuthProvider; - iconClassName?: string; - href: Route; - }; +type OAuthButtonProps = Omit, 'children'> & { + provider: TAuth.OAuthProvider; + iconClassName?: string; + href: Route; +}; export function OAuthButton({ provider, className, iconClassName, - size = 'icon', - variant = 'outline', href, ...props }: OAuthButtonProps) { const meta = CAuth.OAUTH_PROVIDERS[provider]; - if (!meta) return null; - const Icon = meta.icon; - return ( ); diff --git a/src/pages/profile/model/useConnectOauthProvider.ts b/src/pages/profile/api/useConnectOauthProvider.ts similarity index 91% rename from src/pages/profile/model/useConnectOauthProvider.ts rename to src/pages/profile/api/useConnectOauthProvider.ts index a4032bf..dbf46dc 100644 --- a/src/pages/profile/model/useConnectOauthProvider.ts +++ b/src/pages/profile/api/useConnectOauthProvider.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 UseConnectOAuthProviderOptions = Omit< UseMutationOptions, diff --git a/src/pages/profile/model/useConnectedAccounts.ts b/src/pages/profile/api/useConnectedAccounts.ts similarity index 95% rename from src/pages/profile/model/useConnectedAccounts.ts rename to src/pages/profile/api/useConnectedAccounts.ts index 98c002c..8518a9d 100644 --- a/src/pages/profile/model/useConnectedAccounts.ts +++ b/src/pages/profile/api/useConnectedAccounts.ts @@ -4,7 +4,6 @@ import { AuthQueries } from 'entities/auth'; import { useMemo } from 'react'; export function useConnectedAccounts() { - // провайдеры const available = useQuery(AuthQueries.getOAuthProviders()); const connected = useQuery(AuthQueries.getConnectedOAuthProviders()); diff --git a/src/pages/profile/model/useDisconnectOauthProvider.ts b/src/pages/profile/api/useDisconnectOauthProvider.ts similarity index 91% rename from src/pages/profile/model/useDisconnectOauthProvider.ts rename to src/pages/profile/api/useDisconnectOauthProvider.ts index 2f6bec0..ca7464c 100644 --- a/src/pages/profile/model/useDisconnectOauthProvider.ts +++ b/src/pages/profile/api/useDisconnectOauthProvider.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 UseDisconnectOAuthProviderOptions = Omit< UseMutationOptions, 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 929cb02..e505e4f 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/model/useConnectedAccounts'; +import { useConnectedAccounts } from 'pages/profile/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 f62c1b2..7ac92bf 100644 --- a/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx +++ b/src/pages/profile/ui/me-page/account-section/OAuthManageButton.tsx @@ -4,10 +4,11 @@ import { TAuth } from 'entities/auth'; import { CAuth } from 'entities/auth'; import { useCallback, type ComponentProps } from 'react'; import { Button } from 'shared/ui'; -import { useConnectOAuthProvider } from '../../../model/useConnectOauthProvider'; -import { useDisconnectOAuthProvider } from '../../../model/useDisconnectOauthProvider'; +import { useConnectOAuthProvider } from '../../../api/useConnectOauthProvider'; +import { useDisconnectOAuthProvider } from '../../../api/useDisconnectOauthProvider'; import { env } from 'shared/config'; import { toast } from 'sonner'; +import Image from 'next/image'; type OAuthManageButtonProps = ComponentProps & { provider: TAuth.OAuthProvider; @@ -41,8 +42,7 @@ export function OAuthManageButton({ provider, label, isLinked, ...props }: OAuth } }, [connect, disconnect, isLinked, provider]); - const providerConfig = CAuth.OAUTH_PROVIDERS[provider]; - const Icon = providerConfig.icon; + const meta = CAuth.OAUTH_PROVIDERS[provider]; return (