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: временно убрал страницу. Вернуть, когда появится функциональность прав и ролей
15 changes: 12 additions & 3 deletions app/(protected)/user/(user)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { PageLayout } from 'app/layouts/PageLayout';
import { profileTabs } from 'pages/profile';
import { Bell, Settings } from 'lucide-react';
import { routes } from 'shared/config';
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
title="Профиль"
description="Управляйте данными аккаунта, безопасностью и уведомлениями."
tabs={profileTabs}
>
{children}
<div className="grid gap-4 lg:grid-cols-[max-content_1fr]">
<VerticalTabsNav className="max-lg:flex-row" tabs={tabs} />
{children}
</div>
</PageLayout>
);
}
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
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';
22 changes: 6 additions & 16 deletions src/pages/profile/ui/me-page/IdentityItem.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,34 @@
'use client';

import { Item, ItemActions, ItemContent, ItemMedia } from 'shared/ui';
import { Item, ItemActions, ItemMedia } from 'shared/ui';
import { UploadAvatar } from 'features/upload-avatar';
import { SignOut } from 'features/auth/sign-out';
import { type TUser, UserAvatar } from 'entities/user';

type AccountIdentityItemProps = {
profile: TUser.UserResponse['profile'];
email: string;
};

function IdentityItem({ profile, email }: AccountIdentityItemProps) {
function IdentityItem({ profile }: AccountIdentityItemProps) {
const fullName = `${profile.firstName} ${profile.lastName}`;

return (
<Item className="items-start gap-4">
<ItemMedia>
<Item className="items-center justify-between gap-4">
<ItemMedia className="gap-6">
<UploadAvatar
context="user.avatar"
avatar={
<UserAvatar
wrap={{ className: 'ring-background size-28 shadow-md ring-4' }}
wrap={{ className: 'size-20 ' }}
src={profile.avatar?.medium}
alt={fullName}
fallback={{ firstName: profile.firstName, lastName: profile.lastName }}
/>
}
/>
</ItemMedia>
<ItemContent className="self-center">
<div className="space-y-1">
<p className="text-xl font-semibold">{fullName}</p>
<p className="text-muted-foreground text-sm sm:text-base">{email}</p>
<p className="text-muted-foreground max-w-xl text-sm">
{profile.bio?.trim() || 'Добавьте информацию о себе в профиле'}
</p>
</div>
</ItemContent>
<ItemActions>
<SignOut className="self-start" />
<SignOut size={'lg'} variant={'destructive'} />
</ItemActions>
</Item>
);
Expand Down
7 changes: 3 additions & 4 deletions src/pages/profile/ui/me-page/MePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
CardSection,
CardTitle,
FloatingSaveBar,
Separator,
} from 'shared/ui';
import { IdentityItem } from './IdentityItem';
import { ProfileForm } from './ProfileForm';
Expand Down Expand Up @@ -35,16 +34,16 @@ function MePage() {
<Suspense>
<QueryParamsHandler />
</Suspense>
<div className="space-y-4">
<div className="max-w-5xl space-y-4">
<CardSection
className="space-y-4"
title="Идентификация профиля"
description="Публичная информация о вас."
>
<IdentityItem profile={profile} email={email} />
<Separator />
<IdentityItem profile={profile} />
<ProfileForm form={form} onSubmit={onSubmit} />
</CardSection>

<AccountSection />
<FloatingSaveBar
visible={isDirty}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/profile/ui/me-page/ProfileForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function ProfileForm({ form, onSubmit }: ProfileFormProps) {
<Field data-invalid={fieldState.invalid}>
<FieldLabel>Имя</FieldLabel>
<Input
className="h-9"
aria-label="firstName"
placeholder="Имя"
type="text"
Expand All @@ -36,6 +37,7 @@ function ProfileForm({ form, onSubmit }: ProfileFormProps) {
<Field data-invalid={fieldState.invalid}>
<FieldLabel>Фамилия</FieldLabel>
<Input
className="h-9"
aria-label="lastName"
placeholder="Фамилия"
type="text"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function AccountSection() {

return (
<CardSection
className="grid auto-rows-auto grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-3"
className="flex flex-col"
title="Связанные аккаунты"
description="Управление привязкой к социальным сетям и сервисам"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@

import { authFabricKeys, OAUTH_PROVIDERS, type TAuth } from 'entities/auth';
import { type ComponentProps, useCallback } from 'react';
import { Button } from 'shared/ui';
import { Badge, Button } from 'shared/ui';
import { useConnectOAuthProvider } from '../../../api/useConnectOauthProvider';
import { useDisconnectOAuthProvider } from '../../../api/useDisconnectOauthProvider';
import { env } from 'shared/config';
import { toast } from 'sonner';
import Image from 'next/image';
import { classNames } from 'shared/lib/utils';

type OAuthManageButtonProps = ComponentProps<typeof Button> & {
type OAuthManageButtonProps = ComponentProps<'div'> & {
provider: TAuth.OAuthProvider;
label: string;
isLinked: boolean;
};

export function OAuthManageButton({ provider, label, isLinked, ...props }: OAuthManageButtonProps) {
export function OAuthManageButton({
provider,
className = '',
label,
isLinked,
...props
}: OAuthManageButtonProps) {
const connect = useConnectOAuthProvider();
const disconnect = useDisconnectOAuthProvider();

Expand All @@ -32,6 +39,8 @@ export function OAuthManageButton({ provider, label, isLinked, ...props }: OAuth
} else {
connect.mutate(provider, {
onSuccess: (data) => {
console.log(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();
Expand All @@ -44,20 +53,56 @@ export function OAuthManageButton({ provider, label, isLinked, ...props }: OAuth
const meta = OAUTH_PROVIDERS[provider];

return (
<Button
onClick={handleToggleConnect}
variant={isLinked ? 'destructive' : 'outline'}
size={'lg'}
className="justify-start"
disabled={isLoading}
<div
className={classNames(
'hover:bg-muted flex items-center justify-between gap-2 border-t p-2 transition-colors first:rounded-t-lg first:border-none last:rounded-b-lg',
{},
[className]
)}
{...props}
>
<div className={`${meta.className} w-max rounded-full text-2xl`}>
<Image src={meta.iconSrc} alt={label} width={24} height={24} />
<div className="flex shrink-0 items-center gap-3">
<Image
className={`${meta.className} rounded-lg`}
src={meta.iconSrc}
alt={label}
width={24}
height={24}
/>
<div className="flex flex-col gap-1">
<span className="font-medium">{label}</span>
<OAuthBadge className="lg:hidden" isLinked={isLinked} />
</div>
</div>
<span className="w-full text-center">
{isLinked ? 'Отвязать' : 'Привязать'} {label} аккаунт
</span>
</Button>
<div className="grid items-center gap-3 lg:w-full lg:max-w-[220px] lg:grid-cols-2">
<OAuthBadge isLinked={isLinked} className="hidden text-center lg:block" />
<Button
className="min-w-[106px]"
disabled={isLoading}
onClick={handleToggleConnect}
size={'lg'}
variant={isLinked ? 'outline' : 'default'}
>
{isLinked ? 'Отключить' : 'Подключить'}
</Button>
</div>
</div>
);
}

function OAuthBadge({ isLinked, className = '' }: { isLinked: boolean; className?: string }) {
return (
<Badge
variant={isLinked ? 'outline' : 'destructive'}
className={classNames(
'w-full text-xs leading-none',
{
'border-transparent bg-green-700/10 text-green-700': isLinked,
},
[className]
)}
>
{isLinked ? 'Подключен' : 'Не подключен'}
</Badge>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CardSection, OptionGroup, Skeleton } from 'shared/ui';
import { CardSection, Skeleton } from 'shared/ui';

export function NotificationsPageFallback() {
return (
Expand Down
1 change: 0 additions & 1 deletion src/pages/team/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { teamTabs } from './config/tabs';
export { InvitationsPage } from './ui/invitations/InvitationsPage';
export { MembersPage } from './ui/members/MembersPage';
export { RolesPage } from './ui/roles/RolesPage';
Expand Down
Loading
Loading