Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions app/(protected)/team/(team)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PageLayout } from 'app/layouts/PageLayout';
import { teamTabs } from 'pages/team';
import { Badge } from 'shared/ui';

export default function TeamLayout({ children }: { children: React.ReactNode }) {
Expand All @@ -8,7 +7,6 @@ export default function TeamLayout({ children }: { children: React.ReactNode })
title="Управление командой"
description="Управляйте участниками команды, ожидающими приглашениями, ролями и правами доступа."
badge={<Badge variant="secondary">8 участников</Badge>}
tabs={teamTabs}
>
{children}
</PageLayout>
Expand Down
9 changes: 8 additions & 1 deletion app/(protected)/team/(team)/roles/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export { RolesPage as default } from 'pages/team';
import { notFound } from 'next/navigation';

// export { RolesPage as default } from 'pages/team';
export default function Page() {
notFound();
}

// TODO: временно убрал страницу. Вернуть, когда появится функциональность прав и ролей
23 changes: 17 additions & 6 deletions app/(protected)/user/(user)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { PageLayout } from 'app/layouts/PageLayout';
import { profileTabs } from 'pages/profile';
import { Bell, Settings } from 'lucide-react';
import { routes } from 'shared/config';
import { PageWrapper } from 'widgets/page-wrapper';
import { VerticalTabsNav, type TabNavItem } from 'widgets/tabs-nav';

export const tabs: TabNavItem[] = [
{ key: routes.user.profile(), label: 'Основные настройки', icon: <Settings /> },
{ key: routes.user.notifications(), label: 'Уведомления', icon: <Bell /> },
];

export default function ProfileLayout({ children }: { children: React.ReactNode }) {
return (
<PageLayout
<PageWrapper
title="Профиль"
description="Управляйте данными аккаунта, безопасностью и уведомлениями."
tabs={profileTabs}
className="h-full overflow-auto"
wrap={{ className: 'h-full pb-0' }}
>
{children}
</PageLayout>
<div className="grid h-full gap-4 max-lg:grid-rows-[max-content_1fr] lg:grid-cols-[max-content_1fr]">
<VerticalTabsNav className="max-lg:flex-row" tabs={tabs} />
{children}
</div>
</PageWrapper>
);
}
20 changes: 20 additions & 0 deletions app/(protected)/user/(user)/notifications/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить настройки уведомлений"
description="Попробуйте обновить страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
20 changes: 20 additions & 0 deletions app/(protected)/user/(user)/profile/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить основые настройки профиля"
description="Попробуйте обновить страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
2 changes: 1 addition & 1 deletion src/app/layouts/SidebarLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function SidebarLayout({ children, ...props }: ComponentProps<typeof Side
<SidebarProvider {...props}>
<TeamIdSync />
<AppSidebar />
<SidebarInset className="min-h-screen">
<SidebarInset className="h-screen">
<header className="bg-background sticky top-0 z-50 flex h-14 shrink-0 items-center justify-between gap-2 border-b px-4">
<div className="flex items-center gap-2">
<SidebarTrigger className="-ml-1" />
Expand Down
7 changes: 2 additions & 5 deletions src/entities/user/model/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,11 @@ export const ProfileUpdateBody = z.object({
headline: z.string().max(100, 'Должность слишком длинная').nullable().optional(),
location: z.string().max(100, 'Локация слишком длинная').nullable().optional(),
phone: z.string().max(20, 'Номер телефона слишком длинный').nullable().optional(),
gender: z
.enum(['none', 'male', 'female', 'non_binary', 'other', 'prefer_not_to_say'])
.default('none')
.optional(),
gender: z.enum(['none', 'male', 'female', 'non_binary', 'other', 'prefer_not_to_say']).optional(),
vacationStart: z.string().nullable().optional(),
vacationEnd: z.string().nullable().optional(),
vacationMessage: z.string().max(500, 'Сообщение слишком длинное').nullable().optional(),
pronouns: z.enum(['he_him', 'she_her', 'they_them', 'other', 'none']).default('none').optional(),
pronouns: z.enum(['he_him', 'she_her', 'they_them', 'other', 'none']).optional(),
pronounsCustom: z.string().max(50, 'Максимальная длина 50 символов').nullable().optional(),
bio: z.string().max(1000, 'О себе не более 1000 символов').nullable().optional(),
timezone: z.string().max(50).optional(),
Expand Down
22 changes: 0 additions & 22 deletions src/pages/profile/api/useConnectedAccounts.ts

This file was deleted.

7 changes: 7 additions & 0 deletions src/pages/profile/config/profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { OAuthConnectionStatus } from '../model/profile';

export const OAUTH_BADGE_LABELS = {
connected: 'Подключен',
disconnected: 'Не подключен',
unknown: 'Не удалось проверить',
} as const satisfies Record<OAuthConnectionStatus, string>;
7 changes: 0 additions & 7 deletions src/pages/profile/config/tabs.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/pages/profile/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
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';
2 changes: 2 additions & 0 deletions src/pages/profile/model/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export const ProfileForm = z.object({
});

export type ProfileFormValues = z.infer<typeof ProfileForm>;

export type OAuthConnectionStatus = 'connected' | 'disconnected' | 'unknown';
4 changes: 2 additions & 2 deletions src/pages/profile/model/useMePage.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import { useSuspenseQuery } from '@tanstack/react-query';
import { type TUser, UserQueries } from 'entities/user';
import { useEffect } from 'react';
import { useForm, useFormState } from 'react-hook-form';
import { useUpdateProfile } from '../api/useUpdateProfile';
import { ProfileForm as ProfileFormSchema, type ProfileFormValues } from './profile';

export function useMePage() {
const query = useQuery(UserQueries.getMe());
const query = useSuspenseQuery(UserQueries.getMe());
const profile = query.data?.profile;
const email = query.data?.email;

Expand Down
37 changes: 37 additions & 0 deletions src/pages/profile/model/useOAuthManage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useCallback } from 'react';
import { useConnectOAuthProvider } from '../api/useConnectOauthProvider';
import { useDisconnectOAuthProvider } from '../api/useDisconnectOauthProvider';
import { authFabricKeys, TAuth } from 'entities/auth';
import { toast } from 'sonner';
import { OAuthConnectionStatus } from './profile';
import { env } from 'shared/config';

export function useOAuthManage(provider: TAuth.OAuthProvider, status: OAuthConnectionStatus) {
const connect = useConnectOAuthProvider();
const disconnect = useDisconnectOAuthProvider();

const isPending = connect.isPending || disconnect.isPending;

const handleToggleConnect = useCallback(() => {
if (status === 'connected') {
disconnect.mutate(provider, {
onSuccess: (data, _v, _m, context) => {
context.client.invalidateQueries({ queryKey: authFabricKeys.connectedProviders() });
toast.success(data.message);
},
});
} else if (status === 'disconnected') {
connect.mutate(provider, {
onSuccess: (data) => {
localStorage.setItem('test', JSON.stringify(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, status, provider]);

return { handleToggleConnect, isPending };
}
47 changes: 0 additions & 47 deletions src/pages/profile/ui/me-page/IdentityItem.tsx

This file was deleted.

61 changes: 7 additions & 54 deletions src/pages/profile/ui/me-page/MePage.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,14 @@
'use client';
import dynamic from 'next/dynamic';
import { MePageFallback } from './MePageFallback';

import {
Card,
CardDescription,
CardHeader,
CardSection,
CardTitle,
FloatingSaveBar,
Separator,
} from 'shared/ui';
import { IdentityItem } from './IdentityItem';
import { ProfileForm } from './ProfileForm';
import { useMePage } from '../../model/useMePage';
import { AccountSection } from './account-section/AccountsSection';
import { Suspense } from 'react';
import { QueryParamsHandler } from 'features/handle-query-params';
const MePageContent = dynamic(() => import('./MePageContent').then((mod) => mod.MePage), {
ssr: false,
loading: () => <MePageFallback />,
});

function MePage() {
const { form, profile, email, isDirty, isPending, onSubmit, onDiscard } = useMePage();

if (!profile || !email) {
return (
<Card>
<CardHeader>
<CardTitle>Профиль</CardTitle>
<CardDescription>Данные профиля пока недоступны.</CardDescription>
</CardHeader>
</Card>
);
}

return (
<>
<Suspense>
<QueryParamsHandler />
</Suspense>
<div className="space-y-4">
<CardSection
className="space-y-4"
title="Идентификация профиля"
description="Публичная информация о вас."
>
<IdentityItem profile={profile} email={email} />
<Separator />
<ProfileForm form={form} onSubmit={onSubmit} />
</CardSection>
<AccountSection />
<FloatingSaveBar
visible={isDirty}
onSave={form.handleSubmit(onSubmit)}
onDiscard={onDiscard}
pending={isPending && form.formState.isValidating}
disabledSave={!form.formState.isValid}
/>
</div>
</>
);
return <MePageContent />;
}

export { MePage };
29 changes: 29 additions & 0 deletions src/pages/profile/ui/me-page/MePageContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client';

import { type PropsWithChildren, Suspense } from 'react';
import { QueryParamsHandler } from 'features/handle-query-params';
import { ProfileSection } from './profile-section/ProfileSection';
import { AccountSection } from './account-section/AccountSection';
import { ProfileSectionFallback } from './profile-section/ProfileSectionFallback';

function MePage() {
return (
<>
<Suspense>
<QueryParamsHandler />
</Suspense>
<MePageLayout>
<Suspense fallback={<ProfileSectionFallback />}>
<ProfileSection />
</Suspense>
<AccountSection />
</MePageLayout>
</>
);
}

function MePageLayout({ children }: PropsWithChildren) {
return <div className="max-w-5xl space-y-4 overflow-auto p-1 pb-4">{children}</div>;
}

export { MePage, MePageLayout };
12 changes: 12 additions & 0 deletions src/pages/profile/ui/me-page/MePageFallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AccountSectionFallback } from './account-section/AccountSectionFallback';
import { MePageLayout } from './MePageContent';
import { ProfileSectionFallback } from './profile-section/ProfileSectionFallback';

export function MePageFallback() {
return (
<MePageLayout>
<ProfileSectionFallback />
<AccountSectionFallback />
</MePageLayout>
);
}
Loading
Loading