[frontend]: add settings page
This commit is contained in:
parent
0fc00be0cb
commit
8018d21011
@ -15,14 +15,22 @@
|
|||||||
"@graphql-codegen/typescript": "4.1.6",
|
"@graphql-codegen/typescript": "4.1.6",
|
||||||
"@graphql-codegen/typescript-operations": "4.6.1",
|
"@graphql-codegen/typescript-operations": "4.6.1",
|
||||||
"@graphql-codegen/typescript-react-apollo": "4.3.3",
|
"@graphql-codegen/typescript-react-apollo": "4.3.3",
|
||||||
|
"@hello-pangea/dnd": "18.0.1",
|
||||||
"@hookform/resolvers": "5.2.1",
|
"@hookform/resolvers": "5.2.1",
|
||||||
|
"@pbe/react-yandex-maps": "1.2.5",
|
||||||
|
"@radix-ui/react-alert-dialog": "1.1.14",
|
||||||
"@radix-ui/react-avatar": "1.1.10",
|
"@radix-ui/react-avatar": "1.1.10",
|
||||||
|
"@radix-ui/react-dialog": "1.1.14",
|
||||||
"@radix-ui/react-dropdown-menu": "2.1.15",
|
"@radix-ui/react-dropdown-menu": "2.1.15",
|
||||||
"@radix-ui/react-label": "2.1.7",
|
"@radix-ui/react-label": "2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.1.14",
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
|
"@radix-ui/react-select": "2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "1.2.3",
|
"@radix-ui/react-slot": "1.2.3",
|
||||||
|
"@radix-ui/react-switch": "1.2.5",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@radix-ui/react-tooltip": "1.2.7",
|
"@radix-ui/react-tooltip": "1.2.7",
|
||||||
|
"apollo-upload-client": "18.0.1",
|
||||||
"class-variance-authority": "0.7.1",
|
"class-variance-authority": "0.7.1",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"graphql": "16.11.0",
|
"graphql": "16.11.0",
|
||||||
@ -35,7 +43,9 @@
|
|||||||
"react": "19.1.1",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.1",
|
"react-dom": "19.1.1",
|
||||||
"react-hook-form": "7.61.1",
|
"react-hook-form": "7.61.1",
|
||||||
|
"react-icons": "5.5.0",
|
||||||
"sonner": "2.0.6",
|
"sonner": "2.0.6",
|
||||||
|
"subscriptions-transport-ws": "0.11.0",
|
||||||
"tailwind-merge": "3.3.1",
|
"tailwind-merge": "3.3.1",
|
||||||
"zod": "4.0.14",
|
"zod": "4.0.14",
|
||||||
"zustand": "5.0.7"
|
"zustand": "5.0.7"
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
|
import { UserSettings } from '@/components/features/user/UserSettings';
|
||||||
|
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = await getTranslations('layout.header.headerMenu.profileMenu');
|
const t = await getTranslations('dashboard.settings.header');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: t('dashboard'),
|
title: t('heading'),
|
||||||
|
description: t('description'),
|
||||||
|
robots: { index: false, follow: false },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const DashboardSettings = () => (
|
const DashboardSettings = () => (
|
||||||
<div>
|
<UserSettings />
|
||||||
DashboardSettings page
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default DashboardSettings;
|
export default DashboardSettings;
|
||||||
|
|||||||
19
frontend/src/app/account/deactivate/page.tsx
Normal file
19
frontend/src/app/account/deactivate/page.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
|
import { DeactivateForm } from '@/components/features/auth/forms/DeactivateForm';
|
||||||
|
import { NO_INDEX_PAGE } from '@/libs/constants/seo.constants';
|
||||||
|
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
|
const t = await getTranslations('auth.deactivate');
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('heading'),
|
||||||
|
...NO_INDEX_PAGE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeactivatePage() {
|
||||||
|
return <DeactivateForm />;
|
||||||
|
}
|
||||||
@ -2,11 +2,13 @@ import { Geist } from 'next/font/google';
|
|||||||
import { NextIntlClientProvider } from 'next-intl';
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
import { getLocale, getMessages } from 'next-intl/server';
|
import { getLocale, getMessages } from 'next-intl/server';
|
||||||
|
|
||||||
|
import { ColorSwitcher } from '@/components/ui/elements/ColorSwitcher';
|
||||||
import ApolloClientProvider from '@/providers/ApolloClientProvider';
|
import ApolloClientProvider from '@/providers/ApolloClientProvider';
|
||||||
import { ThemeProvider } from '@/providers/ThemeProvider';
|
import { ThemeProvider } from '@/providers/ThemeProvider';
|
||||||
import { ToastProvider } from '@/providers/ToastProvider';
|
import { ToastProvider } from '@/providers/ToastProvider';
|
||||||
|
|
||||||
import '../styles/globals.css';
|
import '../styles/globals.css';
|
||||||
|
import '../styles/themes.css';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
@ -31,6 +33,8 @@ const RootLayout = async ({
|
|||||||
return (
|
return (
|
||||||
<html suppressHydrationWarning lang={locale}>
|
<html suppressHydrationWarning lang={locale}>
|
||||||
<body className={geistSans.variable}>
|
<body className={geistSans.variable}>
|
||||||
|
<ColorSwitcher />
|
||||||
|
|
||||||
<ApolloClientProvider>
|
<ApolloClientProvider>
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
|
|||||||
179
frontend/src/components/features/auth/forms/DeactivateForm.tsx
Normal file
179
frontend/src/components/features/auth/forms/DeactivateForm.tsx
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import {
|
||||||
|
InputOTP,
|
||||||
|
InputOTPGroup,
|
||||||
|
InputOTPSlot,
|
||||||
|
} from '@/components/ui/common/InputOTP';
|
||||||
|
import { useDeactivateAccountMutation } from '@/graphql/generated/output';
|
||||||
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
import { type TypeDeactivateSchema, deactivateSchema } from '@/schemas/auth/deactivate.schema';
|
||||||
|
|
||||||
|
import AuthWrapper from '../AuthWrapper';
|
||||||
|
|
||||||
|
export const DeactivateForm = () => {
|
||||||
|
const t = useTranslations('auth.deactivate');
|
||||||
|
|
||||||
|
const { exit } = useAuth();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [isShowConfirm, setIsShowConfirm] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<TypeDeactivateSchema>({
|
||||||
|
resolver: zodResolver(deactivateSchema),
|
||||||
|
defaultValues: {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [deactivate, { loading: isLoadingDeactivate }] = useDeactivateAccountMutation({
|
||||||
|
onCompleted(data) {
|
||||||
|
if (data.deactivateAccount.message) {
|
||||||
|
setIsShowConfirm(true);
|
||||||
|
} else {
|
||||||
|
exit();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isValid } = form.formState;
|
||||||
|
|
||||||
|
function onSubmit(data: TypeDeactivateSchema) {
|
||||||
|
deactivate({ variables: { data } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthWrapper
|
||||||
|
backButtonHref="/dashboard/settings"
|
||||||
|
backButtonLabel={t('backButtonLabel')}
|
||||||
|
heading={t('heading')}
|
||||||
|
>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="grid gap-y-3"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
{isShowConfirm
|
||||||
|
? (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="pin"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t('pinLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<InputOTP maxLength={6} {...field}>
|
||||||
|
<InputOTPGroup>
|
||||||
|
<InputOTPSlot index={0} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={1} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={2} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={3} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={4} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={5} />
|
||||||
|
</InputOTPGroup>
|
||||||
|
</InputOTP>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('pinDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t('emailLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingDeactivate}
|
||||||
|
placeholder="john.doe@example.com"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('emailDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t('passwordLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingDeactivate}
|
||||||
|
placeholder="********"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('passwordDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="mt-2 w-full"
|
||||||
|
disabled={!isValid || isLoadingDeactivate}
|
||||||
|
>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</AuthWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
143
frontend/src/components/features/user/UserSettings.tsx
Normal file
143
frontend/src/components/features/user/UserSettings.tsx
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from '@/components/ui/common/Tabs';
|
||||||
|
import { Heading } from '@/components/ui/elements/Heading';
|
||||||
|
|
||||||
|
import { ChangeEmailForm } from './account/ChangeEmailForm';
|
||||||
|
import { ChangePasswordForm } from './account/ChangePasswordForm';
|
||||||
|
import { DeactivateCard } from './account/DeactivateCard';
|
||||||
|
import { WrapperTotp } from './account/totp/WrapperTotp';
|
||||||
|
import { ChangeColorForm } from './appearance/ChangeColorForm';
|
||||||
|
import { ChangeLanguageForm } from './appearance/ChangeLanguageForm';
|
||||||
|
import { ChangeThemeForm } from './appearance/ChangeThemeForm';
|
||||||
|
import { ChangeNotificationsSettingsForm } from './notifications/ChangeNotificationsSettingsForm';
|
||||||
|
import { ChangeAvatarForm } from './profile/ChangeAvatarForm';
|
||||||
|
import { ChangeInfoForm } from './profile/ChangeInfoForm';
|
||||||
|
import { SocialLinksForm } from './profile/social-links-form/SocialLinksForm';
|
||||||
|
import { SessionsList } from './sessions/SessionsList';
|
||||||
|
|
||||||
|
export const UserSettings = () => {
|
||||||
|
const t = useTranslations('dashboard.settings');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="lg:px-10">
|
||||||
|
<Heading
|
||||||
|
description={t('header.description')}
|
||||||
|
size="lg"
|
||||||
|
title={t('header.heading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tabs className="mt-3 w-full" defaultValue="profile">
|
||||||
|
<TabsList className="grid max-w-2xl grid-cols-5">
|
||||||
|
<TabsTrigger value="profile">
|
||||||
|
{t('header.profile')}
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
<TabsTrigger value="account">
|
||||||
|
{t('header.account')}
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
<TabsTrigger value="appearance">
|
||||||
|
{t('header.appearance')}
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
<TabsTrigger value="notifications">
|
||||||
|
{t('header.notifications')}
|
||||||
|
</TabsTrigger>
|
||||||
|
|
||||||
|
<TabsTrigger value="sessions">
|
||||||
|
{t('header.sessions')}
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="profile">
|
||||||
|
<div className="mt-5 space-y-6">
|
||||||
|
<Heading
|
||||||
|
description={t('profile.header.description')}
|
||||||
|
title={t('profile.header.heading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChangeAvatarForm />
|
||||||
|
|
||||||
|
<ChangeInfoForm />
|
||||||
|
|
||||||
|
<SocialLinksForm />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="account">
|
||||||
|
<div className="mt-5 space-y-6">
|
||||||
|
<Heading
|
||||||
|
description={t('account.header.description')}
|
||||||
|
title={t('account.header.heading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChangeEmailForm />
|
||||||
|
|
||||||
|
<ChangePasswordForm />
|
||||||
|
|
||||||
|
<Heading
|
||||||
|
description={t(
|
||||||
|
'account.header.securityDescription',
|
||||||
|
)}
|
||||||
|
title={t('account.header.securityHeading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<WrapperTotp />
|
||||||
|
|
||||||
|
<Heading
|
||||||
|
description={t(
|
||||||
|
'account.header.deactivationDescription',
|
||||||
|
)}
|
||||||
|
title={t('account.header.deactivationHeading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DeactivateCard />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="appearance">
|
||||||
|
<div className="mt-5 space-y-6">
|
||||||
|
<Heading
|
||||||
|
description={t('appearance.header.description')}
|
||||||
|
title={t('appearance.header.heading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChangeThemeForm />
|
||||||
|
|
||||||
|
<ChangeLanguageForm />
|
||||||
|
|
||||||
|
<ChangeColorForm />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="notifications">
|
||||||
|
<div className="mt-5 space-y-6">
|
||||||
|
<Heading
|
||||||
|
description={t('notifications.header.description')}
|
||||||
|
title={t('notifications.header.heading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChangeNotificationsSettingsForm />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="sessions">
|
||||||
|
<div className="mt-5 space-y-6">
|
||||||
|
<Heading
|
||||||
|
description={t('sessions.header.description')}
|
||||||
|
title={t('sessions.header.heading')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SessionsList />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import { Separator } from '@/components/ui/common/Separator';
|
||||||
|
import { Skeleton } from '@/components/ui/common/Skeleton';
|
||||||
|
import { FormWrapper } from '@/components/ui/elements/FormWrapper';
|
||||||
|
import { useChangeEmailMutation } from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
import {
|
||||||
|
type TypeChangeEmailSchema,
|
||||||
|
changeEmailSchema,
|
||||||
|
} from '@/schemas/user/change-email.schema';
|
||||||
|
|
||||||
|
export const ChangeEmailFormSkeleton = () => <Skeleton className="h-64 w-full" />;
|
||||||
|
|
||||||
|
export const ChangeEmailForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.account.email');
|
||||||
|
|
||||||
|
const { user, isLoadingProfile, refetch } = useCurrent();
|
||||||
|
|
||||||
|
const form = useForm<TypeChangeEmailSchema>({
|
||||||
|
resolver: zodResolver(changeEmailSchema),
|
||||||
|
values: {
|
||||||
|
email: user?.email ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [update, { loading: isLoadingUpdate }] = useChangeEmailMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isValid, isDirty } = form.formState;
|
||||||
|
|
||||||
|
function onSubmit(data: TypeChangeEmailSchema) {
|
||||||
|
void update({ variables: { data } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoadingProfile
|
||||||
|
? (
|
||||||
|
<ChangeEmailFormSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<FormWrapper heading={t('heading')}>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="grid gap-y-3"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5">
|
||||||
|
<FormLabel>
|
||||||
|
{t('emailLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingUpdate}
|
||||||
|
placeholder="john.doe@example.com"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('emailDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex justify-end p-5">
|
||||||
|
<Button
|
||||||
|
disabled={!isValid || !isDirty || isLoadingUpdate}
|
||||||
|
>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</FormWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import { Separator } from '@/components/ui/common/Separator';
|
||||||
|
import { Skeleton } from '@/components/ui/common/Skeleton';
|
||||||
|
import { FormWrapper } from '@/components/ui/elements/FormWrapper';
|
||||||
|
import { useChangePasswordMutation } from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
import {
|
||||||
|
type TypeChangePasswordSchema,
|
||||||
|
changePasswordSchema,
|
||||||
|
} from '@/schemas/user/change-password.schema';
|
||||||
|
|
||||||
|
export const ChangePasswordFormSkeleton = () => <Skeleton className="h-96 w-full" />;
|
||||||
|
|
||||||
|
export const ChangePasswordForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.account.password');
|
||||||
|
|
||||||
|
const { isLoadingProfile, refetch } = useCurrent();
|
||||||
|
|
||||||
|
const form = useForm<TypeChangePasswordSchema>({
|
||||||
|
resolver: zodResolver(changePasswordSchema),
|
||||||
|
values: {
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [update, { loading: isLoadingUpdate }] = useChangePasswordMutation({
|
||||||
|
onCompleted() {
|
||||||
|
form.reset();
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isValid } = form.formState;
|
||||||
|
|
||||||
|
function onSubmit(data: TypeChangePasswordSchema) {
|
||||||
|
void update({ variables: { data } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoadingProfile
|
||||||
|
? (
|
||||||
|
<ChangePasswordFormSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<FormWrapper heading={t('heading')}>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="grid gap-y-3"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="oldPassword"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5">
|
||||||
|
<FormLabel>
|
||||||
|
{t('oldPasswordLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingUpdate}
|
||||||
|
placeholder="********"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('oldPasswordDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="newPassword"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5">
|
||||||
|
<FormLabel>
|
||||||
|
{t('newPasswordLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingUpdate}
|
||||||
|
placeholder="********"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('newPasswordDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex justify-end p-5">
|
||||||
|
<Button disabled={!isValid || isLoadingUpdate}>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</FormWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import { CardContainer } from '@/components/ui/elements/CardContainer';
|
||||||
|
import { ConfirmModal } from '@/components/ui/elements/ConfirmModal';
|
||||||
|
|
||||||
|
export const DeactivateCard = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.account.deactivation');
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
description={t('description')}
|
||||||
|
heading={t('heading')}
|
||||||
|
rightContent={(
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
<ConfirmModal
|
||||||
|
heading={t('confirmModal.heading')}
|
||||||
|
message={t('confirmModal.message')}
|
||||||
|
onConfirm={() => { router.push('/account/deactivate'); }}
|
||||||
|
>
|
||||||
|
<Button>
|
||||||
|
{t('button')}
|
||||||
|
</Button>
|
||||||
|
</ConfirmModal>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import { ConfirmModal } from '@/components/ui/elements/ConfirmModal';
|
||||||
|
import { useDisableTotpMutation } from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
|
||||||
|
export const DisableTotp = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.account.twoFactor.disable');
|
||||||
|
|
||||||
|
const { refetch } = useCurrent();
|
||||||
|
|
||||||
|
const [disable, { loading: isLoadingDisable }] = useDisableTotpMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
heading={t('heading')}
|
||||||
|
message={t('message')}
|
||||||
|
onConfirm={async () => disable()}
|
||||||
|
>
|
||||||
|
<Button disabled={isLoadingDisable} variant="secondary">
|
||||||
|
{t('trigger')}
|
||||||
|
</Button>
|
||||||
|
</ConfirmModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/common/Dialog';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import {
|
||||||
|
InputOTP,
|
||||||
|
InputOTPGroup,
|
||||||
|
InputOTPSlot,
|
||||||
|
} from '@/components/ui/common/InputOTP';
|
||||||
|
import {
|
||||||
|
useEnableTotpMutation,
|
||||||
|
useGenerateTotpSecretQuery,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
import {
|
||||||
|
type TypeEnableTotpSchema,
|
||||||
|
enableTotpSchema,
|
||||||
|
} from '@/schemas/user/enable-totp.schema';
|
||||||
|
|
||||||
|
export const EnableTotp = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.account.twoFactor.enable');
|
||||||
|
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { refetch } = useCurrent();
|
||||||
|
|
||||||
|
const { data, loading: isLoadingGenerate } = useGenerateTotpSecretQuery();
|
||||||
|
const twoFactorAuth = data?.generateTotpSecret;
|
||||||
|
|
||||||
|
const form = useForm<TypeEnableTotpSchema>({
|
||||||
|
resolver: zodResolver(enableTotpSchema),
|
||||||
|
defaultValues: {
|
||||||
|
pin: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [enable, { loading: isLoadingEnable }] = useEnableTotpMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
setIsOpen(false);
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isValid } = form.formState;
|
||||||
|
|
||||||
|
function onSubmit(values: TypeEnableTotpSchema) {
|
||||||
|
void enable({
|
||||||
|
variables: {
|
||||||
|
data: {
|
||||||
|
secret: twoFactorAuth?.secret ?? '',
|
||||||
|
pin: values.pin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>
|
||||||
|
{t('trigger')}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{t('heading')}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{twoFactorAuth?.qrcodeUrl
|
||||||
|
? t('qrInstructions')
|
||||||
|
: ''}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Image
|
||||||
|
alt="QR"
|
||||||
|
className="rounded-lg"
|
||||||
|
height={300}
|
||||||
|
src={twoFactorAuth?.qrcodeUrl ?? ''}
|
||||||
|
width={300}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="text-center text-sm text-muted-foreground">
|
||||||
|
{twoFactorAuth?.secret
|
||||||
|
? t('secretCodeLabel')
|
||||||
|
+ twoFactorAuth.secret
|
||||||
|
: ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="pin"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col justify-center max-sm:items-center">
|
||||||
|
<FormLabel>
|
||||||
|
{t('pinLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<InputOTP maxLength={6} {...field}>
|
||||||
|
<InputOTPGroup>
|
||||||
|
<InputOTPSlot index={0} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={1} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={2} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={3} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={4} />
|
||||||
|
|
||||||
|
<InputOTPSlot index={5} />
|
||||||
|
</InputOTPGroup>
|
||||||
|
</InputOTP>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('pinDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!isValid
|
||||||
|
|| isLoadingGenerate
|
||||||
|
|| isLoadingEnable
|
||||||
|
}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { Skeleton } from '@/components/ui/common/Skeleton';
|
||||||
|
import { CardContainer } from '@/components/ui/elements/CardContainer';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
|
||||||
|
import { DisableTotp } from './DisableTotp';
|
||||||
|
import { EnableTotp } from './EnableTotp';
|
||||||
|
|
||||||
|
export const WrapperTotpSkeleton = () => <Skeleton className="h-24 w-full" />;
|
||||||
|
|
||||||
|
export const WrapperTotp = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.account.twoFactor');
|
||||||
|
|
||||||
|
const { user, isLoadingProfile } = useCurrent();
|
||||||
|
|
||||||
|
return isLoadingProfile
|
||||||
|
? (
|
||||||
|
<WrapperTotpSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<CardContainer
|
||||||
|
description={t('description')}
|
||||||
|
heading={t('heading')}
|
||||||
|
rightContent={(
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
{!user?.isTotpEnabled ? <EnableTotp /> : <DisableTotp />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Check } from 'lucide-react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { CardContainer } from '@/components/ui/elements/CardContainer';
|
||||||
|
import { useConfig } from '@/hooks/useConfig';
|
||||||
|
import { BASE_COLORS } from '@/libs/constants/colors.constants';
|
||||||
|
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
export const ChangeColorForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.appearance.color');
|
||||||
|
|
||||||
|
const config = useConfig();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
description={t('description')}
|
||||||
|
heading={t('heading')}
|
||||||
|
rightContent={(
|
||||||
|
<div className="grid grid-cols-4 gap-2 md:grid-cols-8">
|
||||||
|
{BASE_COLORS.map((theme) => {
|
||||||
|
const isActive = config.theme === theme.name;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={theme.name}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
'--theme-primary': `hsl(${theme.color})`,
|
||||||
|
} as CSSProperties
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
onClick={() => { config.setTheme(theme.name); }}
|
||||||
|
>
|
||||||
|
<span className="flex size-9 shrink-0 -translate-x-1 items-center justify-center rounded-lg bg-(--theme-primary) hover:border-2 hover:border-foreground">
|
||||||
|
{isActive ? <Check className="size-5 text-white" /> : null}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
|
import { useTransition } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Form, FormField } from '@/components/ui/common/Form';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/common/Select';
|
||||||
|
import { CardContainer } from '@/components/ui/elements/CardContainer';
|
||||||
|
import { setCurrentLanguage } from '@/libs/i18n/language';
|
||||||
|
import {
|
||||||
|
changeLanguageSchema,
|
||||||
|
} from '@/schemas/user/change-language.schema';
|
||||||
|
|
||||||
|
import type { TypeChangeLanguageSchema } from '@/schemas/user/change-language.schema';
|
||||||
|
|
||||||
|
const languages = {
|
||||||
|
ru: 'Русский',
|
||||||
|
en: 'English',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ChangeLanguageForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.appearance.language');
|
||||||
|
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
const locale = useLocale();
|
||||||
|
|
||||||
|
const form = useForm<TypeChangeLanguageSchema>({
|
||||||
|
resolver: zodResolver(changeLanguageSchema),
|
||||||
|
values: {
|
||||||
|
language: locale,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(data: TypeChangeLanguageSchema) {
|
||||||
|
startTransition(async () => {
|
||||||
|
try {
|
||||||
|
await setCurrentLanguage(data.language);
|
||||||
|
} catch (error) {
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
description={t('description')}
|
||||||
|
heading={t('heading')}
|
||||||
|
rightContent={(
|
||||||
|
<Form {...form}>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="language"
|
||||||
|
render={({ field }) => (
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
field.onChange(value);
|
||||||
|
void form.handleSubmit(onSubmit)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
<SelectValue placeholder={t('selectPlaceholder')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
{Object.entries(languages).map(
|
||||||
|
([code, name]) => (
|
||||||
|
<SelectItem
|
||||||
|
key={code}
|
||||||
|
disabled={isPending}
|
||||||
|
value={code}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</SelectItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Form, FormField } from '@/components/ui/common/Form';
|
||||||
|
import { ToggleCard } from '@/components/ui/elements/ToggleCard';
|
||||||
|
import {
|
||||||
|
changeThemeSchema,
|
||||||
|
} from '@/schemas/user/change-theme.schema';
|
||||||
|
|
||||||
|
import type { TypeChangeThemeSchema } from '@/schemas/user/change-theme.schema';
|
||||||
|
|
||||||
|
export const ChangeThemeForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.appearance.theme');
|
||||||
|
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
|
const form = useForm<TypeChangeThemeSchema>({
|
||||||
|
resolver: zodResolver(changeThemeSchema),
|
||||||
|
values: {
|
||||||
|
theme: theme === 'dark' ? 'dark' : 'light',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onChange = (value: boolean) => {
|
||||||
|
const newTheme = value ? 'dark' : 'light';
|
||||||
|
|
||||||
|
setTheme(newTheme);
|
||||||
|
form.setValue('theme', newTheme);
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="theme"
|
||||||
|
render={({ field }) => (
|
||||||
|
<ToggleCard
|
||||||
|
description={t('description')}
|
||||||
|
heading={t('heading')}
|
||||||
|
value={field.value === 'dark'}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Form, FormField } from '@/components/ui/common/Form';
|
||||||
|
import {
|
||||||
|
ToggleCard,
|
||||||
|
ToggleCardSkeleton,
|
||||||
|
} from '@/components/ui/elements/ToggleCard';
|
||||||
|
import { useChangeNotificationsSettingsMutation } from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
import {
|
||||||
|
type TypeChangeNotificationsSettingsSchema,
|
||||||
|
changeNotificationsSettingsSchema,
|
||||||
|
} from '@/schemas/user/change-notifications-settings.schema';
|
||||||
|
|
||||||
|
export const ChangeNotificationsSettingsForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.notifications');
|
||||||
|
|
||||||
|
const { user, isLoadingProfile, refetch } = useCurrent();
|
||||||
|
|
||||||
|
const form = useForm<TypeChangeNotificationsSettingsSchema>({
|
||||||
|
resolver: zodResolver(changeNotificationsSettingsSchema),
|
||||||
|
values: {
|
||||||
|
siteNotifications:
|
||||||
|
user?.notificationSettings?.siteNotifications ?? false,
|
||||||
|
telegramNotifications:
|
||||||
|
user?.notificationSettings?.telegramNotifications ?? false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [update, { loading: isLoadingUpdate }] = useChangeNotificationsSettingsMutation({
|
||||||
|
onCompleted(data) {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
|
||||||
|
if (data.changeNotificationSettings.telegramAuthToken) {
|
||||||
|
window.open(
|
||||||
|
`https://t.me/ksv741_teastream_bot?start=${data.changeNotificationSettings.telegramAuthToken}`,
|
||||||
|
'_blank',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onChange(
|
||||||
|
field: keyof TypeChangeNotificationsSettingsSchema,
|
||||||
|
value: boolean,
|
||||||
|
) {
|
||||||
|
form.setValue(field, value);
|
||||||
|
|
||||||
|
void update({
|
||||||
|
variables: {
|
||||||
|
data: { ...form.getValues(), [field]: value },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoadingProfile
|
||||||
|
? Array.from({ length: 2 }).map((_, index) => (
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
<ToggleCardSkeleton key={index} />
|
||||||
|
))
|
||||||
|
: (
|
||||||
|
<Form {...form}>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="siteNotifications"
|
||||||
|
render={({ field }) => (
|
||||||
|
<ToggleCard
|
||||||
|
description={t('siteNotifications.description')}
|
||||||
|
heading={t('siteNotifications.heading')}
|
||||||
|
isDisabled={isLoadingUpdate}
|
||||||
|
value={field.value}
|
||||||
|
onChange={(value) => { onChange('siteNotifications', value); }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="telegramNotifications"
|
||||||
|
render={({ field }) => (
|
||||||
|
<ToggleCard
|
||||||
|
description={t('telegramNotifications.description')}
|
||||||
|
heading={t('telegramNotifications.heading')}
|
||||||
|
isDisabled={isLoadingUpdate}
|
||||||
|
value={field.value}
|
||||||
|
onChange={(value) => { onChange('telegramNotifications', value); }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,159 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { Trash } from 'lucide-react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { type ChangeEvent, useRef } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import { Form, FormField } from '@/components/ui/common/Form';
|
||||||
|
import { Skeleton } from '@/components/ui/common/Skeleton';
|
||||||
|
import { ChannelAvatar } from '@/components/ui/elements/ChannelAvatar';
|
||||||
|
import { ConfirmModal } from '@/components/ui/elements/ConfirmModal';
|
||||||
|
import { FormWrapper } from '@/components/ui/elements/FormWrapper';
|
||||||
|
import {
|
||||||
|
useChangeProfileAvatarMutation,
|
||||||
|
useRemoveProfileAvatarMutation,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
import {
|
||||||
|
type TypeUploadFileSchema,
|
||||||
|
uploadFileSchema,
|
||||||
|
} from '@/schemas/upload-file.schema';
|
||||||
|
|
||||||
|
export const ChangeAvatarFormSkeleton = () => <Skeleton className="h-52 w-full" />;
|
||||||
|
|
||||||
|
export const ChangeAvatarForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.profile.avatar');
|
||||||
|
|
||||||
|
const { user, isLoadingProfile, refetch } = useCurrent();
|
||||||
|
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const form = useForm<TypeUploadFileSchema>({
|
||||||
|
resolver: zodResolver(uploadFileSchema),
|
||||||
|
values: {
|
||||||
|
file: user?.avatar ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [update, { loading: isLoadingUpdate }] = useChangeProfileAvatarMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successUpdateMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorUpdateMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [remove, { loading: isLoadingRemove }] = useRemoveProfileAvatarMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successRemoveMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorRemoveMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleImageChange(event: ChangeEvent<HTMLInputElement>) {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
void update({ variables: { avatar: file } });
|
||||||
|
form.setValue('file', file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return isLoadingProfile
|
||||||
|
? (
|
||||||
|
<ChangeAvatarFormSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<FormWrapper heading={t('heading')}>
|
||||||
|
<Form {...form}>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="file"
|
||||||
|
render={({ field }) => (
|
||||||
|
<div className="px-5 pb-5">
|
||||||
|
<div className="w-full items-center space-x-6 lg:flex">
|
||||||
|
<ChannelAvatar
|
||||||
|
channel={{
|
||||||
|
name: user.name,
|
||||||
|
avatar:
|
||||||
|
field.value instanceof File
|
||||||
|
? URL.createObjectURL(
|
||||||
|
field.value,
|
||||||
|
)
|
||||||
|
: field.value,
|
||||||
|
}}
|
||||||
|
size="xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-x-3">
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
className="hidden"
|
||||||
|
type="file"
|
||||||
|
onChange={handleImageChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
isLoadingUpdate
|
||||||
|
|| isLoadingRemove
|
||||||
|
}
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => inputRef.current?.click()}
|
||||||
|
>
|
||||||
|
{t('updateButton')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{user?.avatar
|
||||||
|
? (
|
||||||
|
<ConfirmModal
|
||||||
|
heading={t(
|
||||||
|
'confirmModal.heading',
|
||||||
|
)}
|
||||||
|
message={t(
|
||||||
|
'confirmModal.message',
|
||||||
|
)}
|
||||||
|
onConfirm={async () => remove()}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
isLoadingUpdate
|
||||||
|
|| isLoadingRemove
|
||||||
|
}
|
||||||
|
size="lgIcon"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<Trash className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</ConfirmModal>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t('info')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</FormWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
165
frontend/src/components/features/user/profile/ChangeInfoForm.tsx
Normal file
165
frontend/src/components/features/user/profile/ChangeInfoForm.tsx
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import { Separator } from '@/components/ui/common/Separator';
|
||||||
|
import { Skeleton } from '@/components/ui/common/Skeleton';
|
||||||
|
import { Textarea } from '@/components/ui/common/Textarea';
|
||||||
|
import { FormWrapper } from '@/components/ui/elements/FormWrapper';
|
||||||
|
import { useChangeProfileInfoMutation } from '@/graphql/generated/output';
|
||||||
|
import { useCurrent } from '@/hooks/useCurrent';
|
||||||
|
import {
|
||||||
|
type TypeChangeInfoSchema,
|
||||||
|
changeInfoSchema,
|
||||||
|
} from '@/schemas/user/change-info.schema';
|
||||||
|
|
||||||
|
export const ChangeInfoFormSkeleton = () => <Skeleton className="h-96 w-full" />;
|
||||||
|
|
||||||
|
export const ChangeInfoForm = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.profile.info');
|
||||||
|
|
||||||
|
const { user, isLoadingProfile, refetch } = useCurrent();
|
||||||
|
|
||||||
|
const form = useForm<TypeChangeInfoSchema>({
|
||||||
|
resolver: zodResolver(changeInfoSchema),
|
||||||
|
values: {
|
||||||
|
name: user?.name ?? '',
|
||||||
|
displayName: user?.displayName ?? '',
|
||||||
|
bio: user?.bio ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [update, { loading: isLoadingUpdate }] = useChangeProfileInfoMutation(
|
||||||
|
{
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { isValid, isDirty } = form.formState;
|
||||||
|
|
||||||
|
function onSubmit(data: TypeChangeInfoSchema) {
|
||||||
|
void update({ variables: { data } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoadingProfile
|
||||||
|
? (
|
||||||
|
<ChangeInfoFormSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<FormWrapper heading={t('heading')}>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="grid gap-y-3"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5">
|
||||||
|
<FormLabel>
|
||||||
|
{t('usernameLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingUpdate}
|
||||||
|
placeholder={t('usernamePlaceholder')}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('usernameDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="displayName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5 pb-3">
|
||||||
|
<FormLabel>
|
||||||
|
{t('displayNameLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingUpdate}
|
||||||
|
placeholder={t(
|
||||||
|
'displayNamePlaceholder',
|
||||||
|
)}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('displayNameDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="bio"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5 pb-3">
|
||||||
|
<FormLabel>
|
||||||
|
{t('bioLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
disabled={isLoadingUpdate}
|
||||||
|
placeholder={t('bioPlaceholder')}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('bioDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex justify-end p-5">
|
||||||
|
<Button
|
||||||
|
disabled={!isValid || !isDirty || isLoadingUpdate}
|
||||||
|
>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</FormWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { GripVertical, Pencil, Trash2 } from 'lucide-react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import {
|
||||||
|
type FindSocialLinksQuery,
|
||||||
|
useFindSocialLinksQuery,
|
||||||
|
useRemoveSocialLinkMutation,
|
||||||
|
useUpdateSocialLinkMutation,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
import {
|
||||||
|
type TypeSocialLinksSchema,
|
||||||
|
socialLinksSchema,
|
||||||
|
} from '@/schemas/user/social-links.schema';
|
||||||
|
|
||||||
|
import type { DraggableProvided } from '@hello-pangea/dnd';
|
||||||
|
|
||||||
|
type SocialLinkItemProps = {
|
||||||
|
socialLink: FindSocialLinksQuery['findSocialLinks'][0];
|
||||||
|
provided: DraggableProvided;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SocialLinkItem = ({ socialLink, provided }: SocialLinkItemProps) => {
|
||||||
|
const t = useTranslations('dashboard.settings.profile.socialLinks.editForm');
|
||||||
|
|
||||||
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { refetch } = useFindSocialLinksQuery();
|
||||||
|
|
||||||
|
const form = useForm<TypeSocialLinksSchema>({
|
||||||
|
resolver: zodResolver(socialLinksSchema),
|
||||||
|
values: {
|
||||||
|
title: socialLink.title ?? '',
|
||||||
|
url: socialLink.url ?? '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isValid, isDirty } = form.formState;
|
||||||
|
|
||||||
|
function toggleEditing(id: string | null) {
|
||||||
|
setEditingId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [update, { loading: isLoadingUpdate }] = useUpdateSocialLinkMutation({
|
||||||
|
onCompleted() {
|
||||||
|
toggleEditing(null);
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successUpdateMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorUpdateMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [remove, { loading: isLoadingRemove }] = useRemoveSocialLinkMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successRemoveMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorRemoveMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSubmit(data: TypeSocialLinksSchema) {
|
||||||
|
void update({ variables: { id: socialLink.id, data } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={provided.innerRef}
|
||||||
|
className="mb-4 flex items-center gap-x-2 rounded-md border border-border bg-background text-sm"
|
||||||
|
{...provided.draggableProps}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="rounded-l-md border-r border-r-border px-2 py-9 text-foreground transition"
|
||||||
|
{...provided.dragHandleProps}
|
||||||
|
>
|
||||||
|
<GripVertical className="size-5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 px-2">
|
||||||
|
{editingId === socialLink.id
|
||||||
|
? (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="flex gap-x-6"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="w-96 space-y-2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
className="h-8"
|
||||||
|
disabled={
|
||||||
|
isLoadingUpdate
|
||||||
|
|| isLoadingRemove
|
||||||
|
}
|
||||||
|
placeholder="YouTube"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="url"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
className="h-8"
|
||||||
|
disabled={
|
||||||
|
isLoadingUpdate
|
||||||
|
|| isLoadingRemove
|
||||||
|
}
|
||||||
|
placeholder="https://youtube.com/@TeaCoder52"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => { toggleEditing(null); }}
|
||||||
|
>
|
||||||
|
{t('cancelButton')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!isDirty
|
||||||
|
|| !isValid
|
||||||
|
|| isLoadingUpdate
|
||||||
|
|| isLoadingRemove
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<h2 className="text-[17px] font-semibold text-foreground">
|
||||||
|
{socialLink.title}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{socialLink.url}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="ml-auto flex items-center gap-x-2 pr-4">
|
||||||
|
{editingId !== socialLink.id && (
|
||||||
|
<Button
|
||||||
|
size="lgIcon"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => { toggleEditing(socialLink.id); }}
|
||||||
|
>
|
||||||
|
<Pencil className="size-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="lgIcon"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={async () => remove({ variables: { id: socialLink.id } })}
|
||||||
|
>
|
||||||
|
<Trash2 className="size-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
} from '@/components/ui/common/Form';
|
||||||
|
import { Input } from '@/components/ui/common/Input';
|
||||||
|
import { Separator } from '@/components/ui/common/Separator';
|
||||||
|
import { Skeleton } from '@/components/ui/common/Skeleton';
|
||||||
|
import { FormWrapper } from '@/components/ui/elements/FormWrapper';
|
||||||
|
import {
|
||||||
|
useCreateSocialLinkMutation,
|
||||||
|
useFindSocialLinksQuery,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
import {
|
||||||
|
type TypeSocialLinksSchema,
|
||||||
|
socialLinksSchema,
|
||||||
|
} from '@/schemas/user/social-links.schema';
|
||||||
|
|
||||||
|
import { SocialLinksList } from './SocialLinksList';
|
||||||
|
|
||||||
|
export const SocialLinksFormSkeleton = () => <Skeleton className="h-72 w-full" />;
|
||||||
|
|
||||||
|
export const SocialLinksForm = () => {
|
||||||
|
const t = useTranslations(
|
||||||
|
'dashboard.settings.profile.socialLinks.createForm',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loading: isLoadingLinks, refetch } = useFindSocialLinksQuery();
|
||||||
|
|
||||||
|
const form = useForm<TypeSocialLinksSchema>({
|
||||||
|
resolver: zodResolver(socialLinksSchema),
|
||||||
|
defaultValues: {
|
||||||
|
title: '',
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [create, { loading: isLoadingCreate }] = useCreateSocialLinkMutation({
|
||||||
|
onCompleted() {
|
||||||
|
form.reset();
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isValid } = form.formState;
|
||||||
|
|
||||||
|
function onSubmit(data: TypeSocialLinksSchema) {
|
||||||
|
void create({ variables: { data } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLoadingLinks
|
||||||
|
? (
|
||||||
|
<SocialLinksFormSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<FormWrapper heading={t('heading')}>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
className="grid gap-y-3"
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="title"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5">
|
||||||
|
<FormLabel>
|
||||||
|
{t('titleLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingCreate}
|
||||||
|
placeholder={t('titlePlaceholder')}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('titleDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="url"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="px-5 pb-3">
|
||||||
|
<FormLabel>
|
||||||
|
{t('urlLabel')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
disabled={isLoadingCreate}
|
||||||
|
placeholder={t('urlPlaceholder')}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t('urlDescription')}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex justify-end p-5">
|
||||||
|
<Button disabled={!isValid || isLoadingCreate}>
|
||||||
|
{t('submitButton')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<SocialLinksList />
|
||||||
|
</FormWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DragDropContext,
|
||||||
|
Draggable,
|
||||||
|
type DropResult,
|
||||||
|
Droppable,
|
||||||
|
} from '@hello-pangea/dnd';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Separator } from '@/components/ui/common/Separator';
|
||||||
|
import {
|
||||||
|
useFindSocialLinksQuery,
|
||||||
|
useReorderSocialLinksMutation,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
|
||||||
|
import { SocialLinkItem } from './SocialLinkItem';
|
||||||
|
|
||||||
|
export const SocialLinksList = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.profile.socialLinks');
|
||||||
|
|
||||||
|
const { data, refetch } = useFindSocialLinksQuery();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const items = data?.findSocialLinks ?? [];
|
||||||
|
|
||||||
|
const [socialLinks, setSocialLinks] = useState(items);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSocialLinks(items);
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
const [reorder, { loading: isLoadingReorder }] = useReorderSocialLinksMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successReorderMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorReorderMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onDragEnd = (result: DropResult) => {
|
||||||
|
if (!result.destination) return;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const items = Array.from(socialLinks);
|
||||||
|
const [reorderItem] = items.splice(result.source.index, 1);
|
||||||
|
|
||||||
|
items.splice(result.destination.index, 0, reorderItem);
|
||||||
|
|
||||||
|
const bulkUpdateData = items.map((socialLink, index) => ({
|
||||||
|
id: socialLink.id,
|
||||||
|
position: index,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setSocialLinks(items);
|
||||||
|
|
||||||
|
void reorder({ variables: { list: bulkUpdateData } });
|
||||||
|
};
|
||||||
|
|
||||||
|
return socialLinks.length
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="mt-5 px-5">
|
||||||
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
|
<Droppable droppableId="socialLinks">
|
||||||
|
{(dropProvided) => (
|
||||||
|
<div
|
||||||
|
{...dropProvided.droppableProps}
|
||||||
|
ref={dropProvided.innerRef}
|
||||||
|
>
|
||||||
|
{socialLinks.map((socialLink, index) => (
|
||||||
|
<Draggable
|
||||||
|
key={socialLink.id}
|
||||||
|
draggableId={socialLink.id}
|
||||||
|
index={index}
|
||||||
|
isDragDisabled={isLoadingReorder}
|
||||||
|
>
|
||||||
|
{(dragProvided) => (
|
||||||
|
<SocialLinkItem
|
||||||
|
key={socialLink.id}
|
||||||
|
provided={dragProvided}
|
||||||
|
socialLink={socialLink}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Draggable>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{dropProvided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
};
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/common/Button';
|
||||||
|
import { CardContainer } from '@/components/ui/elements/CardContainer';
|
||||||
|
import { ConfirmModal } from '@/components/ui/elements/ConfirmModal';
|
||||||
|
import {
|
||||||
|
type FindSessionsByUserQuery,
|
||||||
|
useFindSessionsByUserQuery,
|
||||||
|
useRemoveSessionMutation,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
import { getBrowserIcon } from '@/utils/get-browser-icon';
|
||||||
|
|
||||||
|
import { SessionModal } from './SessionModal';
|
||||||
|
|
||||||
|
type SessionItemProps = {
|
||||||
|
session: FindSessionsByUserQuery['findSessionsByUser'][0];
|
||||||
|
isCurrentSession?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SessionItem = ({ session, isCurrentSession }: SessionItemProps) => {
|
||||||
|
const t = useTranslations('dashboard.settings.sessions.sessionItem');
|
||||||
|
|
||||||
|
const { refetch } = useFindSessionsByUserQuery();
|
||||||
|
|
||||||
|
const [remove, { loading: isLoadingRemove }] = useRemoveSessionMutation({
|
||||||
|
onCompleted() {
|
||||||
|
void refetch();
|
||||||
|
toast.success(t('successMessage'));
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
toast.error(t('errorMessage'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Icon = getBrowserIcon(session.metadata.device.browser);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
description={`${session.metadata.location.country}, ${session.metadata.location.city}`}
|
||||||
|
heading={`${session.metadata.device.browser}, ${session.metadata.device.os}`}
|
||||||
|
Icon={Icon}
|
||||||
|
rightContent={(
|
||||||
|
<div className="flex items-center gap-x-4">
|
||||||
|
{!isCurrentSession && (
|
||||||
|
<ConfirmModal
|
||||||
|
heading={t('confirmModal.heading')}
|
||||||
|
message={t('confirmModal.message')}
|
||||||
|
onConfirm={async () => remove({ variables: { id: session.id } })}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
disabled={isLoadingRemove}
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
|
{t('deleteButton')}
|
||||||
|
</Button>
|
||||||
|
</ConfirmModal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SessionModal session={session}>
|
||||||
|
<Button>
|
||||||
|
{t('detailsButton')}
|
||||||
|
</Button>
|
||||||
|
</SessionModal>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
110
frontend/src/components/features/user/sessions/SessionModal.tsx
Normal file
110
frontend/src/components/features/user/sessions/SessionModal.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { Map, Placemark, YMaps } from '@pbe/react-yandex-maps';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/common/Dialog';
|
||||||
|
import { formatDate } from '@/utils/format-date';
|
||||||
|
|
||||||
|
import type { FindSessionsByUserQuery } from '@/graphql/generated/output';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
type SessionModalProps = {
|
||||||
|
session: FindSessionsByUserQuery['findSessionsByUser'][0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SessionModal = ({
|
||||||
|
children,
|
||||||
|
session,
|
||||||
|
}: PropsWithChildren<SessionModalProps>) => {
|
||||||
|
const t = useTranslations('dashboard.settings.sessions.sessionModal');
|
||||||
|
|
||||||
|
const center = [
|
||||||
|
session.metadata.location.latitude,
|
||||||
|
session.metadata.location.longitude,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
{children}
|
||||||
|
</DialogTrigger>
|
||||||
|
|
||||||
|
<DialogContent>
|
||||||
|
<DialogTitle className="text-xl">
|
||||||
|
{t('heading')}
|
||||||
|
</DialogTitle>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-medium">
|
||||||
|
{t('device')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
{session.metadata.device.browser}
|
||||||
|
|
||||||
|
,
|
||||||
|
{' '}
|
||||||
|
|
||||||
|
{session.metadata.device.os}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-medium">
|
||||||
|
{t('location')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
{session.metadata.location.country}
|
||||||
|
|
||||||
|
,
|
||||||
|
{' '}
|
||||||
|
|
||||||
|
{session.metadata.location.city}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-medium">
|
||||||
|
{t('ipAddress')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
{session.metadata.ip}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-medium">
|
||||||
|
{t('createdAt')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
{formatDate(session.createdAt, true)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<YMaps>
|
||||||
|
<div style={{ width: '100%', height: '300px' }}>
|
||||||
|
<Map
|
||||||
|
defaultState={{
|
||||||
|
center,
|
||||||
|
zoom: 11,
|
||||||
|
}}
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Placemark geometry={center} />
|
||||||
|
</Map>
|
||||||
|
</div>
|
||||||
|
</YMaps>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { Heading } from '@/components/ui/elements/Heading';
|
||||||
|
import { ToggleCardSkeleton } from '@/components/ui/elements/ToggleCard';
|
||||||
|
import {
|
||||||
|
useFindCurrentSessionQuery,
|
||||||
|
useFindSessionsByUserQuery,
|
||||||
|
} from '@/graphql/generated/output';
|
||||||
|
|
||||||
|
import { SessionItem } from './SessionItem';
|
||||||
|
|
||||||
|
export const SessionsList = () => {
|
||||||
|
const t = useTranslations('dashboard.settings.sessions');
|
||||||
|
|
||||||
|
const { data: sessionData, loading: isLoadingCurrent } = useFindCurrentSessionQuery();
|
||||||
|
const currentSession = sessionData?.findCurrentSession;
|
||||||
|
|
||||||
|
const { data: sessionsData, loading: isLoadingSessions } = useFindSessionsByUserQuery();
|
||||||
|
const sessions = sessionsData?.findSessionsByUser ?? [];
|
||||||
|
|
||||||
|
if (!currentSession) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Heading size="sm" title={t('info.current')} />
|
||||||
|
|
||||||
|
{isLoadingCurrent
|
||||||
|
? (
|
||||||
|
<ToggleCardSkeleton />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<SessionItem isCurrentSession session={currentSession} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Heading size="sm" title={t('info.active')} />
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line no-nested-ternary */}
|
||||||
|
{isLoadingSessions
|
||||||
|
? Array.from({ length: 3 }).map((_, index) => (
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
<ToggleCardSkeleton key={index} />
|
||||||
|
))
|
||||||
|
: sessions.length
|
||||||
|
? sessions.map((session, index) => (
|
||||||
|
<SessionItem key={session.id} session={session} />
|
||||||
|
))
|
||||||
|
: (
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
{t('info.notFound')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
147
frontend/src/components/ui/common/AlertDialog.tsx
Normal file
147
frontend/src/components/ui/common/AlertDialog.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
type HTMLAttributes,
|
||||||
|
forwardRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
import { buttonVariants } from './Button';
|
||||||
|
|
||||||
|
const AlertDialog = AlertDialogPrimitive.Root;
|
||||||
|
|
||||||
|
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
||||||
|
|
||||||
|
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
||||||
|
|
||||||
|
const AlertDialogOverlay = forwardRef<
|
||||||
|
ComponentRef<typeof AlertDialogPrimitive.Overlay>,
|
||||||
|
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const AlertDialogContent = forwardRef<
|
||||||
|
ComponentRef<typeof AlertDialogPrimitive.Content>,
|
||||||
|
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
));
|
||||||
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const AlertDialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col space-y-2 text-center sm:text-left',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
AlertDialogHeader.displayName = 'AlertDialogHeader';
|
||||||
|
|
||||||
|
const AlertDialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
AlertDialogFooter.displayName = 'AlertDialogFooter';
|
||||||
|
|
||||||
|
const AlertDialogTitle = forwardRef<
|
||||||
|
ComponentRef<typeof AlertDialogPrimitive.Title>,
|
||||||
|
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-lg font-semibold', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const AlertDialogDescription = forwardRef<
|
||||||
|
ComponentRef<typeof AlertDialogPrimitive.Description>,
|
||||||
|
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('text-sm text-muted-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
const AlertDialogAction = forwardRef<
|
||||||
|
ComponentRef<typeof AlertDialogPrimitive.Action>,
|
||||||
|
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
ref={ref}
|
||||||
|
className={cn(buttonVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
||||||
|
|
||||||
|
const AlertDialogCancel = forwardRef<
|
||||||
|
ComponentRef<typeof AlertDialogPrimitive.Cancel>,
|
||||||
|
ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: 'secondary' }),
|
||||||
|
'mt-2 sm:mt-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
};
|
||||||
130
frontend/src/components/ui/common/Dialog.tsx
Normal file
130
frontend/src/components/ui/common/Dialog.tsx
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
type HTMLAttributes,
|
||||||
|
forwardRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger;
|
||||||
|
|
||||||
|
const DialogPortal = DialogPrimitive.Portal;
|
||||||
|
|
||||||
|
const DialogClose = DialogPrimitive.Close;
|
||||||
|
|
||||||
|
const DialogOverlay = forwardRef<
|
||||||
|
ComponentRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
|
const DialogContent = forwardRef<
|
||||||
|
ComponentRef<typeof DialogPrimitive.Content>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<X className="size-4" />
|
||||||
|
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
));
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DialogHeader.displayName = 'DialogHeader';
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
DialogFooter.displayName = 'DialogFooter';
|
||||||
|
|
||||||
|
const DialogTitle = forwardRef<
|
||||||
|
ComponentRef<typeof DialogPrimitive.Title>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'text-lg font-semibold leading-none tracking-wide',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||||
|
|
||||||
|
const DialogDescription = forwardRef<
|
||||||
|
ComponentRef<typeof DialogPrimitive.Description>,
|
||||||
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn('textcn-sm text-muted-foreground', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogPortal,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
};
|
||||||
170
frontend/src/components/ui/common/Select.tsx
Normal file
170
frontend/src/components/ui/common/Select.tsx
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||||
|
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
forwardRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root;
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group;
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value;
|
||||||
|
|
||||||
|
const SelectTrigger = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex h-10 w-full items-center justify-between rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
));
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const SelectScrollUpButton = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-default items-center justify-center py-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
));
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||||
|
|
||||||
|
const SelectScrollDownButton = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-default items-center justify-center py-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
));
|
||||||
|
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
|
||||||
|
|
||||||
|
const SelectContent = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.Content>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({
|
||||||
|
className, children, position = 'popper', ...props
|
||||||
|
}, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||||
|
position === 'popper'
|
||||||
|
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
'p-1',
|
||||||
|
position === 'popper'
|
||||||
|
&& 'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
));
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const SelectLabel = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.Label>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||||
|
|
||||||
|
const SelectItem = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.Item>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectPrimitive.ItemText>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
));
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||||
|
|
||||||
|
const SelectSeparator = forwardRef<
|
||||||
|
ComponentRef<typeof SelectPrimitive.Separator>,
|
||||||
|
ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
};
|
||||||
33
frontend/src/components/ui/common/Switch.tsx
Normal file
33
frontend/src/components/ui/common/Switch.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
forwardRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
const Switch = forwardRef<
|
||||||
|
ComponentRef<typeof SwitchPrimitives.Root>,
|
||||||
|
ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-muted-foreground',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
));
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||||
|
|
||||||
|
export { Switch };
|
||||||
61
frontend/src/components/ui/common/Tabs.tsx
Normal file
61
frontend/src/components/ui/common/Tabs.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||||
|
import {
|
||||||
|
type ComponentPropsWithoutRef,
|
||||||
|
type ComponentRef,
|
||||||
|
forwardRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root;
|
||||||
|
|
||||||
|
const TabsList = forwardRef<
|
||||||
|
ComponentRef<typeof TabsPrimitive.List>,
|
||||||
|
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex h-10 items-center justify-center rounded-md bg-card p-1 text-muted-foreground',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||||
|
|
||||||
|
const TabsTrigger = forwardRef<
|
||||||
|
ComponentRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'data-data-[state=active]:shadow-sm inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium text-foreground ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-accent',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const TabsContent = forwardRef<
|
||||||
|
ComponentRef<typeof TabsPrimitive.Content>,
|
||||||
|
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Tabs, TabsContent, TabsList, TabsTrigger,
|
||||||
|
};
|
||||||
19
frontend/src/components/ui/common/Textarea.tsx
Normal file
19
frontend/src/components/ui/common/Textarea.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { type ComponentProps, forwardRef } from 'react';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
const Textarea = forwardRef<HTMLTextAreaElement, ComponentProps<'textarea'>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<textarea
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
'flex max-h-[80px] min-h-[80px] w-full rounded-md border border-border bg-input px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:border-primary focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Textarea.displayName = 'Textarea';
|
||||||
|
|
||||||
|
export { Textarea };
|
||||||
68
frontend/src/components/ui/elements/CardContainer.tsx
Normal file
68
frontend/src/components/ui/elements/CardContainer.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
import { Card } from '../common/Card';
|
||||||
|
|
||||||
|
import type { LucideIcon } from 'lucide-react';
|
||||||
|
import type { PropsWithChildren, ReactNode } from 'react';
|
||||||
|
import type { IconType } from 'react-icons';
|
||||||
|
|
||||||
|
type CardContainerProps = {
|
||||||
|
heading: string;
|
||||||
|
description?: string;
|
||||||
|
Icon?: IconType | LucideIcon;
|
||||||
|
isRightContentFull?: boolean;
|
||||||
|
rightContent?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CardContainer = ({
|
||||||
|
heading,
|
||||||
|
description,
|
||||||
|
Icon,
|
||||||
|
isRightContentFull,
|
||||||
|
rightContent,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<CardContainerProps>) => (
|
||||||
|
<Card className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex flex-row items-center gap-x-4">
|
||||||
|
{Icon
|
||||||
|
? (
|
||||||
|
<div className="rounded-full bg-foreground p-2.5">
|
||||||
|
<Icon className="size-7 text-secondary" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h2 className="font-semibold tracking-wide">
|
||||||
|
{heading}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{description
|
||||||
|
? (
|
||||||
|
<p className="max-w-4xl text-sm text-muted-foreground">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{rightContent
|
||||||
|
? (
|
||||||
|
<div className={cn(isRightContentFull && 'ml-6 w-full')}>
|
||||||
|
{rightContent}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{children
|
||||||
|
? (
|
||||||
|
<div className="mt-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
@ -34,10 +34,14 @@ export const ChannelAvatar = ({ size, channel, isLive }: ChannelAvatarProps) =>
|
|||||||
isLive && 'ring-2 ring-rose-500',
|
isLive && 'ring-2 ring-rose-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{channel.avatar
|
||||||
|
? (
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
src={getMediaSource(channel.avatar)}
|
src={getMediaSource(channel.avatar)}
|
||||||
/>
|
/>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
|
||||||
<AvatarFallback
|
<AvatarFallback
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
23
frontend/src/components/ui/elements/ColorSwitcher.tsx
Normal file
23
frontend/src/components/ui/elements/ColorSwitcher.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useConfig } from '@/hooks/useConfig';
|
||||||
|
|
||||||
|
export const ColorSwitcher = () => {
|
||||||
|
const { theme } = useConfig();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.classList.forEach((className) => {
|
||||||
|
if ((/^theme.*/).exec(className)) {
|
||||||
|
document.body.classList.remove(className);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (theme) {
|
||||||
|
document.body.classList.add(`theme-${theme}`);
|
||||||
|
}
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
62
frontend/src/components/ui/elements/ConfirmModal.tsx
Normal file
62
frontend/src/components/ui/elements/ConfirmModal.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from '../common/AlertDialog';
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
type ConfirmModalProps = {
|
||||||
|
heading: string;
|
||||||
|
message: string;
|
||||||
|
onConfirm: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfirmModal = ({
|
||||||
|
children,
|
||||||
|
heading,
|
||||||
|
message,
|
||||||
|
onConfirm,
|
||||||
|
}: PropsWithChildren<ConfirmModalProps>) => {
|
||||||
|
const t = useTranslations('components.confirmModal');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
{children}
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
{heading}
|
||||||
|
</AlertDialogTitle>
|
||||||
|
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{message}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>
|
||||||
|
{t('cancel')}
|
||||||
|
</AlertDialogCancel>
|
||||||
|
|
||||||
|
<AlertDialogAction onClick={onConfirm}>
|
||||||
|
{t('continue')}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
26
frontend/src/components/ui/elements/FormWrapper.tsx
Normal file
26
frontend/src/components/ui/elements/FormWrapper.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
Card, CardContent, CardHeader, CardTitle,
|
||||||
|
} from '../common/Card';
|
||||||
|
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
type FormWrapperProps = {
|
||||||
|
heading: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormWrapper = ({
|
||||||
|
children,
|
||||||
|
heading,
|
||||||
|
}: PropsWithChildren<FormWrapperProps>) => (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="p-4">
|
||||||
|
<CardTitle className="text-lg">
|
||||||
|
{heading}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent className="p-0">
|
||||||
|
{children}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
43
frontend/src/components/ui/elements/Heading.tsx
Normal file
43
frontend/src/components/ui/elements/Heading.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { type VariantProps, cva } from 'class-variance-authority';
|
||||||
|
|
||||||
|
import { cn } from '@/utils/tw-merge';
|
||||||
|
|
||||||
|
const headingSizes = cva('', {
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: 'text-lg',
|
||||||
|
default: 'text-2xl',
|
||||||
|
lg: 'text-4xl',
|
||||||
|
xl: 'text-5xl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type HeadingProps = VariantProps<typeof headingSizes> & {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Heading = ({ size, title, description }: HeadingProps) => (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1
|
||||||
|
className={cn(
|
||||||
|
'font-semibold text-foreground',
|
||||||
|
headingSizes({ size }),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{description
|
||||||
|
? (
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
34
frontend/src/components/ui/elements/ToggleCard.tsx
Normal file
34
frontend/src/components/ui/elements/ToggleCard.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Skeleton } from '../common/Skeleton';
|
||||||
|
import { Switch } from '../common/Switch';
|
||||||
|
|
||||||
|
import { CardContainer } from './CardContainer';
|
||||||
|
|
||||||
|
type ToggleCardProps = {
|
||||||
|
heading: string;
|
||||||
|
description: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
value: boolean;
|
||||||
|
onChange: (value: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToggleCard = ({
|
||||||
|
heading,
|
||||||
|
description,
|
||||||
|
isDisabled,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: ToggleCardProps) => (
|
||||||
|
<CardContainer
|
||||||
|
description={description}
|
||||||
|
heading={heading}
|
||||||
|
rightContent={(
|
||||||
|
<Switch
|
||||||
|
checked={value}
|
||||||
|
disabled={isDisabled}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ToggleCardSkeleton = () => <Skeleton className="mt-6 h-20 w-full" />;
|
||||||
@ -164,7 +164,7 @@ export type Mutation = {
|
|||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
changeChatSettings: StreamModel;
|
changeChatSettings: StreamModel;
|
||||||
changeEmail: UserModel;
|
changeEmail: UserModel;
|
||||||
changeNotificationSettigs: ChangeNotificationsSettingsResponse;
|
changeNotificationSettings: ChangeNotificationsSettingsResponse;
|
||||||
changePassword: UserModel;
|
changePassword: UserModel;
|
||||||
changeProfileAvatar: Scalars['Boolean']['output'];
|
changeProfileAvatar: Scalars['Boolean']['output'];
|
||||||
changeProfileInfo: UserModel;
|
changeProfileInfo: UserModel;
|
||||||
@ -208,7 +208,7 @@ export type MutationChangeEmailArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationChangeNotificationSettigsArgs = {
|
export type MutationChangeNotificationSettingsArgs = {
|
||||||
data: ChangeNotificationSettingsInput;
|
data: ChangeNotificationSettingsInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -578,7 +578,7 @@ export type UserModel = {
|
|||||||
isVerified: Scalars['Boolean']['output'];
|
isVerified: Scalars['Boolean']['output'];
|
||||||
name: Scalars['String']['output'];
|
name: Scalars['String']['output'];
|
||||||
notification: Array<NotificationModel>;
|
notification: Array<NotificationModel>;
|
||||||
notificationSettings: NotificationSettingsModel;
|
notificationSettings?: Maybe<NotificationSettingsModel>;
|
||||||
password: Scalars['String']['output'];
|
password: Scalars['String']['output'];
|
||||||
socialLink: Array<SocialLinkModel>;
|
socialLink: Array<SocialLinkModel>;
|
||||||
stream: StreamModel;
|
stream: StreamModel;
|
||||||
@ -603,6 +603,13 @@ export type CreateUserMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type CreateUserMutation = { __typename?: 'Mutation', createUser: { __typename?: 'UserModel', name: string, password: string, email: string } };
|
export type CreateUserMutation = { __typename?: 'Mutation', createUser: { __typename?: 'UserModel', name: string, password: string, email: string } };
|
||||||
|
|
||||||
|
export type DeactivateAccountMutationVariables = Exact<{
|
||||||
|
data: DeactivateAccountInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type DeactivateAccountMutation = { __typename?: 'Mutation', deactivateAccount: { __typename?: 'AuthModel', message?: string | null, user?: { __typename?: 'UserModel', isDeactivated: boolean } | null } };
|
||||||
|
|
||||||
export type LoginUserMutationVariables = Exact<{
|
export type LoginUserMutationVariables = Exact<{
|
||||||
data: LoginInput;
|
data: LoginInput;
|
||||||
}>;
|
}>;
|
||||||
@ -636,10 +643,93 @@ export type VerifyAccountMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type VerifyAccountMutation = { __typename?: 'Mutation', verifyAccount: { __typename?: 'AuthModel', message?: string | null, user?: { __typename?: 'UserModel', isEmailVerified: boolean } | null } };
|
export type VerifyAccountMutation = { __typename?: 'Mutation', verifyAccount: { __typename?: 'AuthModel', message?: string | null, user?: { __typename?: 'UserModel', isEmailVerified: boolean } | null } };
|
||||||
|
|
||||||
export type FindRecommendedChannelsQueryVariables = Exact<{ [key: string]: never; }>;
|
export type ChangeEmailMutationVariables = Exact<{
|
||||||
|
data: ChangeEmailInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type FindRecommendedChannelsQuery = { __typename?: 'Query', findRecommendedChannels: Array<{ __typename?: 'UserModel', id: string, name: string, avatar?: string | null, isVerified: boolean, stream: { __typename?: 'StreamModel', isLive: boolean } }> };
|
export type ChangeEmailMutation = { __typename?: 'Mutation', changeEmail: { __typename?: 'UserModel', email: string, id: string } };
|
||||||
|
|
||||||
|
export type ChangeNotificationsSettingsMutationVariables = Exact<{
|
||||||
|
data: ChangeNotificationSettingsInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ChangeNotificationsSettingsMutation = { __typename?: 'Mutation', changeNotificationSettings: { __typename?: 'ChangeNotificationsSettingsResponse', telegramAuthToken?: string | null, notificationSettings: { __typename?: 'NotificationSettingsModel', siteNotifications: boolean, telegramNotifications: boolean } } };
|
||||||
|
|
||||||
|
export type ChangePasswordMutationVariables = Exact<{
|
||||||
|
data: ChangePasswordInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ChangePasswordMutation = { __typename?: 'Mutation', changePassword: { __typename?: 'UserModel', name: string, id: string } };
|
||||||
|
|
||||||
|
export type ChangeProfileAvatarMutationVariables = Exact<{
|
||||||
|
avatar: Scalars['Upload']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ChangeProfileAvatarMutation = { __typename?: 'Mutation', changeProfileAvatar: boolean };
|
||||||
|
|
||||||
|
export type ChangeProfileInfoMutationVariables = Exact<{
|
||||||
|
data: ChangeProfileInfoInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ChangeProfileInfoMutation = { __typename?: 'Mutation', changeProfileInfo: { __typename?: 'UserModel', name: string, id: string, displayName: string } };
|
||||||
|
|
||||||
|
export type CreateSocialLinkMutationVariables = Exact<{
|
||||||
|
data: SocialLinkInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type CreateSocialLinkMutation = { __typename?: 'Mutation', createSocialLink: { __typename?: 'SocialLinkModel', id: string } };
|
||||||
|
|
||||||
|
export type DisableTotpMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type DisableTotpMutation = { __typename?: 'Mutation', disableTotp: boolean };
|
||||||
|
|
||||||
|
export type EnableTotpMutationVariables = Exact<{
|
||||||
|
data: EnableTotpInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type EnableTotpMutation = { __typename?: 'Mutation', enableTotp: boolean };
|
||||||
|
|
||||||
|
export type RemoveProfileAvatarMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type RemoveProfileAvatarMutation = { __typename?: 'Mutation', removeProfileAvatar: boolean };
|
||||||
|
|
||||||
|
export type RemoveSessionMutationVariables = Exact<{
|
||||||
|
id: Scalars['String']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type RemoveSessionMutation = { __typename?: 'Mutation', removeSession: boolean };
|
||||||
|
|
||||||
|
export type RemoveSocialLinkMutationVariables = Exact<{
|
||||||
|
id: Scalars['String']['input'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type RemoveSocialLinkMutation = { __typename?: 'Mutation', removeSocialLink: boolean };
|
||||||
|
|
||||||
|
export type ReorderSocialLinksMutationVariables = Exact<{
|
||||||
|
list: Array<SocialLinkOrderInput> | SocialLinkOrderInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ReorderSocialLinksMutation = { __typename?: 'Mutation', reorderSocialLink: boolean };
|
||||||
|
|
||||||
|
export type UpdateSocialLinkMutationVariables = Exact<{
|
||||||
|
id: Scalars['String']['input'];
|
||||||
|
data: SocialLinkInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type UpdateSocialLinkMutation = { __typename?: 'Mutation', updateSocialLink: { __typename?: 'SocialLinkModel', id: string } };
|
||||||
|
|
||||||
export type FindChannelByUsernameQueryVariables = Exact<{
|
export type FindChannelByUsernameQueryVariables = Exact<{
|
||||||
name: Scalars['String']['input'];
|
name: Scalars['String']['input'];
|
||||||
@ -648,6 +738,16 @@ export type FindChannelByUsernameQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type FindChannelByUsernameQuery = { __typename?: 'Query', findChannelByUsername: { __typename?: 'UserModel', name: string, avatar?: string | null, displayName: string, stream: { __typename?: 'StreamModel', title: string } } };
|
export type FindChannelByUsernameQuery = { __typename?: 'Query', findChannelByUsername: { __typename?: 'UserModel', name: string, avatar?: string | null, displayName: string, stream: { __typename?: 'StreamModel', title: string } } };
|
||||||
|
|
||||||
|
export type FindRecommendedChannelsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type FindRecommendedChannelsQuery = { __typename?: 'Query', findRecommendedChannels: Array<{ __typename?: 'UserModel', id: string, name: string, avatar?: string | null, isVerified: boolean, stream: { __typename?: 'StreamModel', isLive: boolean } }> };
|
||||||
|
|
||||||
|
export type FindCurrentSessionQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type FindCurrentSessionQuery = { __typename?: 'Query', findCurrentSession: { __typename?: 'SessionModel', id: string, createdAt: string, metadata: { __typename?: 'SessionMetadataModel', ip: string, location: { __typename?: 'LocationModel', country: string, city: string, latitude: number, longitude: number }, device: { __typename?: 'DeviceModel', browser: string, os: string } } } };
|
||||||
|
|
||||||
export type FindNotificationByUserQueryVariables = Exact<{ [key: string]: never; }>;
|
export type FindNotificationByUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@ -661,7 +761,22 @@ export type FindUnreadNotificationsCountQuery = { __typename?: 'Query', findUnre
|
|||||||
export type FindProfileQueryVariables = Exact<{ [key: string]: never; }>;
|
export type FindProfileQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type FindProfileQuery = { __typename?: 'Query', findProfile: { __typename?: 'UserModel', avatar?: string | null, bio?: string | null, createdAt: any, email: string, id: string, name: string, updatedAt: any, isEmailVerified: boolean, isTotpEnabled: boolean, isVerified: boolean, displayName: string, socialLink: Array<{ __typename?: 'SocialLinkModel', title: string, url: string, position: number }> } };
|
export type FindProfileQuery = { __typename?: 'Query', findProfile: { __typename?: 'UserModel', avatar?: string | null, bio?: string | null, createdAt: any, email: string, id: string, name: string, updatedAt: any, isEmailVerified: boolean, isTotpEnabled: boolean, isVerified: boolean, displayName: string, socialLink: Array<{ __typename?: 'SocialLinkModel', title: string, url: string, position: number }>, notificationSettings?: { __typename?: 'NotificationSettingsModel', siteNotifications: boolean, telegramNotifications: boolean } | null } };
|
||||||
|
|
||||||
|
export type FindSessionsByUserQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type FindSessionsByUserQuery = { __typename?: 'Query', findSessionsByUser: Array<{ __typename?: 'SessionModel', id: string, createdAt: string, metadata: { __typename?: 'SessionMetadataModel', ip: string, location: { __typename?: 'LocationModel', country: string, city: string, latitude: number, longitude: number }, device: { __typename?: 'DeviceModel', browser: string, os: string } } }> };
|
||||||
|
|
||||||
|
export type FindSocialLinksQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type FindSocialLinksQuery = { __typename?: 'Query', findSocialLinks: Array<{ __typename?: 'SocialLinkModel', id: string, title: string, url: string, position: number }> };
|
||||||
|
|
||||||
|
export type GenerateTotpSecretQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GenerateTotpSecretQuery = { __typename?: 'Query', generateTotpSecret: { __typename?: 'TotpModel', qrcodeUrl: string, secret: string } };
|
||||||
|
|
||||||
|
|
||||||
export const ClearSessionCookieDocument = gql`
|
export const ClearSessionCookieDocument = gql`
|
||||||
@ -729,6 +844,42 @@ export function useCreateUserMutation(baseOptions?: Apollo.MutationHookOptions<C
|
|||||||
export type CreateUserMutationHookResult = ReturnType<typeof useCreateUserMutation>;
|
export type CreateUserMutationHookResult = ReturnType<typeof useCreateUserMutation>;
|
||||||
export type CreateUserMutationResult = Apollo.MutationResult<CreateUserMutation>;
|
export type CreateUserMutationResult = Apollo.MutationResult<CreateUserMutation>;
|
||||||
export type CreateUserMutationOptions = Apollo.BaseMutationOptions<CreateUserMutation, CreateUserMutationVariables>;
|
export type CreateUserMutationOptions = Apollo.BaseMutationOptions<CreateUserMutation, CreateUserMutationVariables>;
|
||||||
|
export const DeactivateAccountDocument = gql`
|
||||||
|
mutation DeactivateAccount($data: DeactivateAccountInput!) {
|
||||||
|
deactivateAccount(data: $data) {
|
||||||
|
user {
|
||||||
|
isDeactivated
|
||||||
|
}
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type DeactivateAccountMutationFn = Apollo.MutationFunction<DeactivateAccountMutation, DeactivateAccountMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useDeactivateAccountMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useDeactivateAccountMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useDeactivateAccountMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [deactivateAccountMutation, { data, loading, error }] = useDeactivateAccountMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useDeactivateAccountMutation(baseOptions?: Apollo.MutationHookOptions<DeactivateAccountMutation, DeactivateAccountMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<DeactivateAccountMutation, DeactivateAccountMutationVariables>(DeactivateAccountDocument, options);
|
||||||
|
}
|
||||||
|
export type DeactivateAccountMutationHookResult = ReturnType<typeof useDeactivateAccountMutation>;
|
||||||
|
export type DeactivateAccountMutationResult = Apollo.MutationResult<DeactivateAccountMutation>;
|
||||||
|
export type DeactivateAccountMutationOptions = Apollo.BaseMutationOptions<DeactivateAccountMutation, DeactivateAccountMutationVariables>;
|
||||||
export const LoginUserDocument = gql`
|
export const LoginUserDocument = gql`
|
||||||
mutation LoginUser($data: LoginInput!) {
|
mutation LoginUser($data: LoginInput!) {
|
||||||
loginUser(data: $data) {
|
loginUser(data: $data) {
|
||||||
@ -895,51 +1046,428 @@ export function useVerifyAccountMutation(baseOptions?: Apollo.MutationHookOption
|
|||||||
export type VerifyAccountMutationHookResult = ReturnType<typeof useVerifyAccountMutation>;
|
export type VerifyAccountMutationHookResult = ReturnType<typeof useVerifyAccountMutation>;
|
||||||
export type VerifyAccountMutationResult = Apollo.MutationResult<VerifyAccountMutation>;
|
export type VerifyAccountMutationResult = Apollo.MutationResult<VerifyAccountMutation>;
|
||||||
export type VerifyAccountMutationOptions = Apollo.BaseMutationOptions<VerifyAccountMutation, VerifyAccountMutationVariables>;
|
export type VerifyAccountMutationOptions = Apollo.BaseMutationOptions<VerifyAccountMutation, VerifyAccountMutationVariables>;
|
||||||
export const FindRecommendedChannelsDocument = gql`
|
export const ChangeEmailDocument = gql`
|
||||||
query FindRecommendedChannels {
|
mutation ChangeEmail($data: ChangeEmailInput!) {
|
||||||
findRecommendedChannels {
|
changeEmail(data: $data) {
|
||||||
|
email
|
||||||
id
|
id
|
||||||
name
|
|
||||||
avatar
|
|
||||||
isVerified
|
|
||||||
stream {
|
|
||||||
isLive
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export type ChangeEmailMutationFn = Apollo.MutationFunction<ChangeEmailMutation, ChangeEmailMutationVariables>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useFindRecommendedChannelsQuery__
|
* __useChangeEmailMutation__
|
||||||
*
|
*
|
||||||
* To run a query within a React component, call `useFindRecommendedChannelsQuery` and pass it any options that fit your needs.
|
* To run a mutation, you first call `useChangeEmailMutation` within a React component and pass it any options that fit your needs.
|
||||||
* When your component renders, `useFindRecommendedChannelsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
* When your component renders, `useChangeEmailMutation` returns a tuple that includes:
|
||||||
* you can use to render your UI.
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
*
|
*
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const { data, loading, error } = useFindRecommendedChannelsQuery({
|
* const [changeEmailMutation, { data, loading, error }] = useChangeEmailMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useChangeEmailMutation(baseOptions?: Apollo.MutationHookOptions<ChangeEmailMutation, ChangeEmailMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ChangeEmailMutation, ChangeEmailMutationVariables>(ChangeEmailDocument, options);
|
||||||
|
}
|
||||||
|
export type ChangeEmailMutationHookResult = ReturnType<typeof useChangeEmailMutation>;
|
||||||
|
export type ChangeEmailMutationResult = Apollo.MutationResult<ChangeEmailMutation>;
|
||||||
|
export type ChangeEmailMutationOptions = Apollo.BaseMutationOptions<ChangeEmailMutation, ChangeEmailMutationVariables>;
|
||||||
|
export const ChangeNotificationsSettingsDocument = gql`
|
||||||
|
mutation ChangeNotificationsSettings($data: ChangeNotificationSettingsInput!) {
|
||||||
|
changeNotificationSettings(data: $data) {
|
||||||
|
notificationSettings {
|
||||||
|
siteNotifications
|
||||||
|
telegramNotifications
|
||||||
|
}
|
||||||
|
telegramAuthToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ChangeNotificationsSettingsMutationFn = Apollo.MutationFunction<ChangeNotificationsSettingsMutation, ChangeNotificationsSettingsMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useChangeNotificationsSettingsMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useChangeNotificationsSettingsMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useChangeNotificationsSettingsMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [changeNotificationsSettingsMutation, { data, loading, error }] = useChangeNotificationsSettingsMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useChangeNotificationsSettingsMutation(baseOptions?: Apollo.MutationHookOptions<ChangeNotificationsSettingsMutation, ChangeNotificationsSettingsMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ChangeNotificationsSettingsMutation, ChangeNotificationsSettingsMutationVariables>(ChangeNotificationsSettingsDocument, options);
|
||||||
|
}
|
||||||
|
export type ChangeNotificationsSettingsMutationHookResult = ReturnType<typeof useChangeNotificationsSettingsMutation>;
|
||||||
|
export type ChangeNotificationsSettingsMutationResult = Apollo.MutationResult<ChangeNotificationsSettingsMutation>;
|
||||||
|
export type ChangeNotificationsSettingsMutationOptions = Apollo.BaseMutationOptions<ChangeNotificationsSettingsMutation, ChangeNotificationsSettingsMutationVariables>;
|
||||||
|
export const ChangePasswordDocument = gql`
|
||||||
|
mutation ChangePassword($data: ChangePasswordInput!) {
|
||||||
|
changePassword(data: $data) {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ChangePasswordMutationFn = Apollo.MutationFunction<ChangePasswordMutation, ChangePasswordMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useChangePasswordMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useChangePasswordMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useChangePasswordMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [changePasswordMutation, { data, loading, error }] = useChangePasswordMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useChangePasswordMutation(baseOptions?: Apollo.MutationHookOptions<ChangePasswordMutation, ChangePasswordMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ChangePasswordMutation, ChangePasswordMutationVariables>(ChangePasswordDocument, options);
|
||||||
|
}
|
||||||
|
export type ChangePasswordMutationHookResult = ReturnType<typeof useChangePasswordMutation>;
|
||||||
|
export type ChangePasswordMutationResult = Apollo.MutationResult<ChangePasswordMutation>;
|
||||||
|
export type ChangePasswordMutationOptions = Apollo.BaseMutationOptions<ChangePasswordMutation, ChangePasswordMutationVariables>;
|
||||||
|
export const ChangeProfileAvatarDocument = gql`
|
||||||
|
mutation ChangeProfileAvatar($avatar: Upload!) {
|
||||||
|
changeProfileAvatar(avatar: $avatar)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ChangeProfileAvatarMutationFn = Apollo.MutationFunction<ChangeProfileAvatarMutation, ChangeProfileAvatarMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useChangeProfileAvatarMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useChangeProfileAvatarMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useChangeProfileAvatarMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [changeProfileAvatarMutation, { data, loading, error }] = useChangeProfileAvatarMutation({
|
||||||
|
* variables: {
|
||||||
|
* avatar: // value for 'avatar'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useChangeProfileAvatarMutation(baseOptions?: Apollo.MutationHookOptions<ChangeProfileAvatarMutation, ChangeProfileAvatarMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ChangeProfileAvatarMutation, ChangeProfileAvatarMutationVariables>(ChangeProfileAvatarDocument, options);
|
||||||
|
}
|
||||||
|
export type ChangeProfileAvatarMutationHookResult = ReturnType<typeof useChangeProfileAvatarMutation>;
|
||||||
|
export type ChangeProfileAvatarMutationResult = Apollo.MutationResult<ChangeProfileAvatarMutation>;
|
||||||
|
export type ChangeProfileAvatarMutationOptions = Apollo.BaseMutationOptions<ChangeProfileAvatarMutation, ChangeProfileAvatarMutationVariables>;
|
||||||
|
export const ChangeProfileInfoDocument = gql`
|
||||||
|
mutation ChangeProfileInfo($data: ChangeProfileInfoInput!) {
|
||||||
|
changeProfileInfo(data: $data) {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ChangeProfileInfoMutationFn = Apollo.MutationFunction<ChangeProfileInfoMutation, ChangeProfileInfoMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useChangeProfileInfoMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useChangeProfileInfoMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useChangeProfileInfoMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [changeProfileInfoMutation, { data, loading, error }] = useChangeProfileInfoMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useChangeProfileInfoMutation(baseOptions?: Apollo.MutationHookOptions<ChangeProfileInfoMutation, ChangeProfileInfoMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ChangeProfileInfoMutation, ChangeProfileInfoMutationVariables>(ChangeProfileInfoDocument, options);
|
||||||
|
}
|
||||||
|
export type ChangeProfileInfoMutationHookResult = ReturnType<typeof useChangeProfileInfoMutation>;
|
||||||
|
export type ChangeProfileInfoMutationResult = Apollo.MutationResult<ChangeProfileInfoMutation>;
|
||||||
|
export type ChangeProfileInfoMutationOptions = Apollo.BaseMutationOptions<ChangeProfileInfoMutation, ChangeProfileInfoMutationVariables>;
|
||||||
|
export const CreateSocialLinkDocument = gql`
|
||||||
|
mutation CreateSocialLink($data: SocialLinkInput!) {
|
||||||
|
createSocialLink(data: $data) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type CreateSocialLinkMutationFn = Apollo.MutationFunction<CreateSocialLinkMutation, CreateSocialLinkMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useCreateSocialLinkMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useCreateSocialLinkMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useCreateSocialLinkMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [createSocialLinkMutation, { data, loading, error }] = useCreateSocialLinkMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useCreateSocialLinkMutation(baseOptions?: Apollo.MutationHookOptions<CreateSocialLinkMutation, CreateSocialLinkMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<CreateSocialLinkMutation, CreateSocialLinkMutationVariables>(CreateSocialLinkDocument, options);
|
||||||
|
}
|
||||||
|
export type CreateSocialLinkMutationHookResult = ReturnType<typeof useCreateSocialLinkMutation>;
|
||||||
|
export type CreateSocialLinkMutationResult = Apollo.MutationResult<CreateSocialLinkMutation>;
|
||||||
|
export type CreateSocialLinkMutationOptions = Apollo.BaseMutationOptions<CreateSocialLinkMutation, CreateSocialLinkMutationVariables>;
|
||||||
|
export const DisableTotpDocument = gql`
|
||||||
|
mutation DisableTotp {
|
||||||
|
disableTotp
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type DisableTotpMutationFn = Apollo.MutationFunction<DisableTotpMutation, DisableTotpMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useDisableTotpMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useDisableTotpMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useDisableTotpMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [disableTotpMutation, { data, loading, error }] = useDisableTotpMutation({
|
||||||
* variables: {
|
* variables: {
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function useFindRecommendedChannelsQuery(baseOptions?: Apollo.QueryHookOptions<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>) {
|
export function useDisableTotpMutation(baseOptions?: Apollo.MutationHookOptions<DisableTotpMutation, DisableTotpMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
return Apollo.useQuery<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>(FindRecommendedChannelsDocument, options);
|
return Apollo.useMutation<DisableTotpMutation, DisableTotpMutationVariables>(DisableTotpDocument, options);
|
||||||
}
|
}
|
||||||
export function useFindRecommendedChannelsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>) {
|
export type DisableTotpMutationHookResult = ReturnType<typeof useDisableTotpMutation>;
|
||||||
|
export type DisableTotpMutationResult = Apollo.MutationResult<DisableTotpMutation>;
|
||||||
|
export type DisableTotpMutationOptions = Apollo.BaseMutationOptions<DisableTotpMutation, DisableTotpMutationVariables>;
|
||||||
|
export const EnableTotpDocument = gql`
|
||||||
|
mutation EnableTotp($data: EnableTotpInput!) {
|
||||||
|
enableTotp(data: $data)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type EnableTotpMutationFn = Apollo.MutationFunction<EnableTotpMutation, EnableTotpMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useEnableTotpMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useEnableTotpMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useEnableTotpMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [enableTotpMutation, { data, loading, error }] = useEnableTotpMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useEnableTotpMutation(baseOptions?: Apollo.MutationHookOptions<EnableTotpMutation, EnableTotpMutationVariables>) {
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
return Apollo.useLazyQuery<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>(FindRecommendedChannelsDocument, options);
|
return Apollo.useMutation<EnableTotpMutation, EnableTotpMutationVariables>(EnableTotpDocument, options);
|
||||||
}
|
}
|
||||||
export function useFindRecommendedChannelsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>) {
|
export type EnableTotpMutationHookResult = ReturnType<typeof useEnableTotpMutation>;
|
||||||
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
|
export type EnableTotpMutationResult = Apollo.MutationResult<EnableTotpMutation>;
|
||||||
return Apollo.useSuspenseQuery<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>(FindRecommendedChannelsDocument, options);
|
export type EnableTotpMutationOptions = Apollo.BaseMutationOptions<EnableTotpMutation, EnableTotpMutationVariables>;
|
||||||
|
export const RemoveProfileAvatarDocument = gql`
|
||||||
|
mutation RemoveProfileAvatar {
|
||||||
|
removeProfileAvatar
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type RemoveProfileAvatarMutationFn = Apollo.MutationFunction<RemoveProfileAvatarMutation, RemoveProfileAvatarMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useRemoveProfileAvatarMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useRemoveProfileAvatarMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useRemoveProfileAvatarMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [removeProfileAvatarMutation, { data, loading, error }] = useRemoveProfileAvatarMutation({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useRemoveProfileAvatarMutation(baseOptions?: Apollo.MutationHookOptions<RemoveProfileAvatarMutation, RemoveProfileAvatarMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<RemoveProfileAvatarMutation, RemoveProfileAvatarMutationVariables>(RemoveProfileAvatarDocument, options);
|
||||||
}
|
}
|
||||||
export type FindRecommendedChannelsQueryHookResult = ReturnType<typeof useFindRecommendedChannelsQuery>;
|
export type RemoveProfileAvatarMutationHookResult = ReturnType<typeof useRemoveProfileAvatarMutation>;
|
||||||
export type FindRecommendedChannelsLazyQueryHookResult = ReturnType<typeof useFindRecommendedChannelsLazyQuery>;
|
export type RemoveProfileAvatarMutationResult = Apollo.MutationResult<RemoveProfileAvatarMutation>;
|
||||||
export type FindRecommendedChannelsSuspenseQueryHookResult = ReturnType<typeof useFindRecommendedChannelsSuspenseQuery>;
|
export type RemoveProfileAvatarMutationOptions = Apollo.BaseMutationOptions<RemoveProfileAvatarMutation, RemoveProfileAvatarMutationVariables>;
|
||||||
export type FindRecommendedChannelsQueryResult = Apollo.QueryResult<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>;
|
export const RemoveSessionDocument = gql`
|
||||||
|
mutation RemoveSession($id: String!) {
|
||||||
|
removeSession(id: $id)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type RemoveSessionMutationFn = Apollo.MutationFunction<RemoveSessionMutation, RemoveSessionMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useRemoveSessionMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useRemoveSessionMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useRemoveSessionMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [removeSessionMutation, { data, loading, error }] = useRemoveSessionMutation({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useRemoveSessionMutation(baseOptions?: Apollo.MutationHookOptions<RemoveSessionMutation, RemoveSessionMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<RemoveSessionMutation, RemoveSessionMutationVariables>(RemoveSessionDocument, options);
|
||||||
|
}
|
||||||
|
export type RemoveSessionMutationHookResult = ReturnType<typeof useRemoveSessionMutation>;
|
||||||
|
export type RemoveSessionMutationResult = Apollo.MutationResult<RemoveSessionMutation>;
|
||||||
|
export type RemoveSessionMutationOptions = Apollo.BaseMutationOptions<RemoveSessionMutation, RemoveSessionMutationVariables>;
|
||||||
|
export const RemoveSocialLinkDocument = gql`
|
||||||
|
mutation RemoveSocialLink($id: String!) {
|
||||||
|
removeSocialLink(id: $id)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type RemoveSocialLinkMutationFn = Apollo.MutationFunction<RemoveSocialLinkMutation, RemoveSocialLinkMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useRemoveSocialLinkMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useRemoveSocialLinkMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useRemoveSocialLinkMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [removeSocialLinkMutation, { data, loading, error }] = useRemoveSocialLinkMutation({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useRemoveSocialLinkMutation(baseOptions?: Apollo.MutationHookOptions<RemoveSocialLinkMutation, RemoveSocialLinkMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<RemoveSocialLinkMutation, RemoveSocialLinkMutationVariables>(RemoveSocialLinkDocument, options);
|
||||||
|
}
|
||||||
|
export type RemoveSocialLinkMutationHookResult = ReturnType<typeof useRemoveSocialLinkMutation>;
|
||||||
|
export type RemoveSocialLinkMutationResult = Apollo.MutationResult<RemoveSocialLinkMutation>;
|
||||||
|
export type RemoveSocialLinkMutationOptions = Apollo.BaseMutationOptions<RemoveSocialLinkMutation, RemoveSocialLinkMutationVariables>;
|
||||||
|
export const ReorderSocialLinksDocument = gql`
|
||||||
|
mutation ReorderSocialLinks($list: [SocialLinkOrderInput!]!) {
|
||||||
|
reorderSocialLink(list: $list)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ReorderSocialLinksMutationFn = Apollo.MutationFunction<ReorderSocialLinksMutation, ReorderSocialLinksMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useReorderSocialLinksMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useReorderSocialLinksMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useReorderSocialLinksMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [reorderSocialLinksMutation, { data, loading, error }] = useReorderSocialLinksMutation({
|
||||||
|
* variables: {
|
||||||
|
* list: // value for 'list'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useReorderSocialLinksMutation(baseOptions?: Apollo.MutationHookOptions<ReorderSocialLinksMutation, ReorderSocialLinksMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ReorderSocialLinksMutation, ReorderSocialLinksMutationVariables>(ReorderSocialLinksDocument, options);
|
||||||
|
}
|
||||||
|
export type ReorderSocialLinksMutationHookResult = ReturnType<typeof useReorderSocialLinksMutation>;
|
||||||
|
export type ReorderSocialLinksMutationResult = Apollo.MutationResult<ReorderSocialLinksMutation>;
|
||||||
|
export type ReorderSocialLinksMutationOptions = Apollo.BaseMutationOptions<ReorderSocialLinksMutation, ReorderSocialLinksMutationVariables>;
|
||||||
|
export const UpdateSocialLinkDocument = gql`
|
||||||
|
mutation UpdateSocialLink($id: String!, $data: SocialLinkInput!) {
|
||||||
|
updateSocialLink(id: $id, data: $data) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type UpdateSocialLinkMutationFn = Apollo.MutationFunction<UpdateSocialLinkMutation, UpdateSocialLinkMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useUpdateSocialLinkMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useUpdateSocialLinkMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useUpdateSocialLinkMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [updateSocialLinkMutation, { data, loading, error }] = useUpdateSocialLinkMutation({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useUpdateSocialLinkMutation(baseOptions?: Apollo.MutationHookOptions<UpdateSocialLinkMutation, UpdateSocialLinkMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<UpdateSocialLinkMutation, UpdateSocialLinkMutationVariables>(UpdateSocialLinkDocument, options);
|
||||||
|
}
|
||||||
|
export type UpdateSocialLinkMutationHookResult = ReturnType<typeof useUpdateSocialLinkMutation>;
|
||||||
|
export type UpdateSocialLinkMutationResult = Apollo.MutationResult<UpdateSocialLinkMutation>;
|
||||||
|
export type UpdateSocialLinkMutationOptions = Apollo.BaseMutationOptions<UpdateSocialLinkMutation, UpdateSocialLinkMutationVariables>;
|
||||||
export const FindChannelByUsernameDocument = gql`
|
export const FindChannelByUsernameDocument = gql`
|
||||||
query FindChannelByUsername($name: String!) {
|
query FindChannelByUsername($name: String!) {
|
||||||
findChannelByUsername(name: $name) {
|
findChannelByUsername(name: $name) {
|
||||||
@ -985,6 +1513,104 @@ export type FindChannelByUsernameQueryHookResult = ReturnType<typeof useFindChan
|
|||||||
export type FindChannelByUsernameLazyQueryHookResult = ReturnType<typeof useFindChannelByUsernameLazyQuery>;
|
export type FindChannelByUsernameLazyQueryHookResult = ReturnType<typeof useFindChannelByUsernameLazyQuery>;
|
||||||
export type FindChannelByUsernameSuspenseQueryHookResult = ReturnType<typeof useFindChannelByUsernameSuspenseQuery>;
|
export type FindChannelByUsernameSuspenseQueryHookResult = ReturnType<typeof useFindChannelByUsernameSuspenseQuery>;
|
||||||
export type FindChannelByUsernameQueryResult = Apollo.QueryResult<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>;
|
export type FindChannelByUsernameQueryResult = Apollo.QueryResult<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>;
|
||||||
|
export const FindRecommendedChannelsDocument = gql`
|
||||||
|
query FindRecommendedChannels {
|
||||||
|
findRecommendedChannels {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
avatar
|
||||||
|
isVerified
|
||||||
|
stream {
|
||||||
|
isLive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useFindRecommendedChannelsQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useFindRecommendedChannelsQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useFindRecommendedChannelsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useFindRecommendedChannelsQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useFindRecommendedChannelsQuery(baseOptions?: Apollo.QueryHookOptions<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>(FindRecommendedChannelsDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindRecommendedChannelsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>(FindRecommendedChannelsDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindRecommendedChannelsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>) {
|
||||||
|
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSuspenseQuery<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>(FindRecommendedChannelsDocument, options);
|
||||||
|
}
|
||||||
|
export type FindRecommendedChannelsQueryHookResult = ReturnType<typeof useFindRecommendedChannelsQuery>;
|
||||||
|
export type FindRecommendedChannelsLazyQueryHookResult = ReturnType<typeof useFindRecommendedChannelsLazyQuery>;
|
||||||
|
export type FindRecommendedChannelsSuspenseQueryHookResult = ReturnType<typeof useFindRecommendedChannelsSuspenseQuery>;
|
||||||
|
export type FindRecommendedChannelsQueryResult = Apollo.QueryResult<FindRecommendedChannelsQuery, FindRecommendedChannelsQueryVariables>;
|
||||||
|
export const FindCurrentSessionDocument = gql`
|
||||||
|
query FindCurrentSession {
|
||||||
|
findCurrentSession {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
metadata {
|
||||||
|
location {
|
||||||
|
country
|
||||||
|
city
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
}
|
||||||
|
device {
|
||||||
|
browser
|
||||||
|
os
|
||||||
|
}
|
||||||
|
ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useFindCurrentSessionQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useFindCurrentSessionQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useFindCurrentSessionQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useFindCurrentSessionQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useFindCurrentSessionQuery(baseOptions?: Apollo.QueryHookOptions<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>(FindCurrentSessionDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindCurrentSessionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>(FindCurrentSessionDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindCurrentSessionSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>) {
|
||||||
|
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSuspenseQuery<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>(FindCurrentSessionDocument, options);
|
||||||
|
}
|
||||||
|
export type FindCurrentSessionQueryHookResult = ReturnType<typeof useFindCurrentSessionQuery>;
|
||||||
|
export type FindCurrentSessionLazyQueryHookResult = ReturnType<typeof useFindCurrentSessionLazyQuery>;
|
||||||
|
export type FindCurrentSessionSuspenseQueryHookResult = ReturnType<typeof useFindCurrentSessionSuspenseQuery>;
|
||||||
|
export type FindCurrentSessionQueryResult = Apollo.QueryResult<FindCurrentSessionQuery, FindCurrentSessionQueryVariables>;
|
||||||
export const FindNotificationByUserDocument = gql`
|
export const FindNotificationByUserDocument = gql`
|
||||||
query FindNotificationByUser {
|
query FindNotificationByUser {
|
||||||
findNotificationByUser {
|
findNotificationByUser {
|
||||||
@ -1086,6 +1712,10 @@ export const FindProfileDocument = gql`
|
|||||||
url
|
url
|
||||||
position
|
position
|
||||||
}
|
}
|
||||||
|
notificationSettings {
|
||||||
|
siteNotifications
|
||||||
|
telegramNotifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -1121,3 +1751,138 @@ export type FindProfileQueryHookResult = ReturnType<typeof useFindProfileQuery>;
|
|||||||
export type FindProfileLazyQueryHookResult = ReturnType<typeof useFindProfileLazyQuery>;
|
export type FindProfileLazyQueryHookResult = ReturnType<typeof useFindProfileLazyQuery>;
|
||||||
export type FindProfileSuspenseQueryHookResult = ReturnType<typeof useFindProfileSuspenseQuery>;
|
export type FindProfileSuspenseQueryHookResult = ReturnType<typeof useFindProfileSuspenseQuery>;
|
||||||
export type FindProfileQueryResult = Apollo.QueryResult<FindProfileQuery, FindProfileQueryVariables>;
|
export type FindProfileQueryResult = Apollo.QueryResult<FindProfileQuery, FindProfileQueryVariables>;
|
||||||
|
export const FindSessionsByUserDocument = gql`
|
||||||
|
query FindSessionsByUser {
|
||||||
|
findSessionsByUser {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
metadata {
|
||||||
|
location {
|
||||||
|
country
|
||||||
|
city
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
}
|
||||||
|
device {
|
||||||
|
browser
|
||||||
|
os
|
||||||
|
}
|
||||||
|
ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useFindSessionsByUserQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useFindSessionsByUserQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useFindSessionsByUserQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useFindSessionsByUserQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useFindSessionsByUserQuery(baseOptions?: Apollo.QueryHookOptions<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>(FindSessionsByUserDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindSessionsByUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>(FindSessionsByUserDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindSessionsByUserSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>) {
|
||||||
|
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSuspenseQuery<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>(FindSessionsByUserDocument, options);
|
||||||
|
}
|
||||||
|
export type FindSessionsByUserQueryHookResult = ReturnType<typeof useFindSessionsByUserQuery>;
|
||||||
|
export type FindSessionsByUserLazyQueryHookResult = ReturnType<typeof useFindSessionsByUserLazyQuery>;
|
||||||
|
export type FindSessionsByUserSuspenseQueryHookResult = ReturnType<typeof useFindSessionsByUserSuspenseQuery>;
|
||||||
|
export type FindSessionsByUserQueryResult = Apollo.QueryResult<FindSessionsByUserQuery, FindSessionsByUserQueryVariables>;
|
||||||
|
export const FindSocialLinksDocument = gql`
|
||||||
|
query FindSocialLinks {
|
||||||
|
findSocialLinks {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
url
|
||||||
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useFindSocialLinksQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useFindSocialLinksQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useFindSocialLinksQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useFindSocialLinksQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useFindSocialLinksQuery(baseOptions?: Apollo.QueryHookOptions<FindSocialLinksQuery, FindSocialLinksQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<FindSocialLinksQuery, FindSocialLinksQueryVariables>(FindSocialLinksDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindSocialLinksLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindSocialLinksQuery, FindSocialLinksQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<FindSocialLinksQuery, FindSocialLinksQueryVariables>(FindSocialLinksDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindSocialLinksSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindSocialLinksQuery, FindSocialLinksQueryVariables>) {
|
||||||
|
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSuspenseQuery<FindSocialLinksQuery, FindSocialLinksQueryVariables>(FindSocialLinksDocument, options);
|
||||||
|
}
|
||||||
|
export type FindSocialLinksQueryHookResult = ReturnType<typeof useFindSocialLinksQuery>;
|
||||||
|
export type FindSocialLinksLazyQueryHookResult = ReturnType<typeof useFindSocialLinksLazyQuery>;
|
||||||
|
export type FindSocialLinksSuspenseQueryHookResult = ReturnType<typeof useFindSocialLinksSuspenseQuery>;
|
||||||
|
export type FindSocialLinksQueryResult = Apollo.QueryResult<FindSocialLinksQuery, FindSocialLinksQueryVariables>;
|
||||||
|
export const GenerateTotpSecretDocument = gql`
|
||||||
|
query GenerateTotpSecret {
|
||||||
|
generateTotpSecret {
|
||||||
|
qrcodeUrl
|
||||||
|
secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGenerateTotpSecretQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useGenerateTotpSecretQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGenerateTotpSecretQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useGenerateTotpSecretQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGenerateTotpSecretQuery(baseOptions?: Apollo.QueryHookOptions<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>(GenerateTotpSecretDocument, options);
|
||||||
|
}
|
||||||
|
export function useGenerateTotpSecretLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>(GenerateTotpSecretDocument, options);
|
||||||
|
}
|
||||||
|
export function useGenerateTotpSecretSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>) {
|
||||||
|
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useSuspenseQuery<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>(GenerateTotpSecretDocument, options);
|
||||||
|
}
|
||||||
|
export type GenerateTotpSecretQueryHookResult = ReturnType<typeof useGenerateTotpSecretQuery>;
|
||||||
|
export type GenerateTotpSecretLazyQueryHookResult = ReturnType<typeof useGenerateTotpSecretLazyQuery>;
|
||||||
|
export type GenerateTotpSecretSuspenseQueryHookResult = ReturnType<typeof useGenerateTotpSecretSuspenseQuery>;
|
||||||
|
export type GenerateTotpSecretQueryResult = Apollo.QueryResult<GenerateTotpSecretQuery, GenerateTotpSecretQueryVariables>;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
mutation DeactivateAccount($data: DeactivateAccountInput!) {
|
||||||
|
deactivateAccount(data: $data) {
|
||||||
|
user {
|
||||||
|
isDeactivated
|
||||||
|
}
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/src/graphql/mutations/user/ChangeEmail.graphql
Normal file
6
frontend/src/graphql/mutations/user/ChangeEmail.graphql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
mutation ChangeEmail($data: ChangeEmailInput!) {
|
||||||
|
changeEmail(data: $data) {
|
||||||
|
email
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
mutation ChangeNotificationsSettings($data: ChangeNotificationSettingsInput!) {
|
||||||
|
changeNotificationSettings(data: $data) {
|
||||||
|
notificationSettings {
|
||||||
|
siteNotifications
|
||||||
|
telegramNotifications
|
||||||
|
}
|
||||||
|
telegramAuthToken
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
mutation ChangePassword($data: ChangePasswordInput!) {
|
||||||
|
changePassword(data: $data) {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
mutation ChangeProfileAvatar($avatar: Upload!) {
|
||||||
|
changeProfileAvatar(avatar: $avatar)
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
mutation ChangeProfileInfo($data: ChangeProfileInfoInput!) {
|
||||||
|
changeProfileInfo(data: $data) {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
mutation ClearSessionCookie {
|
||||||
|
clearSessionCookie
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
mutation CreateSocialLink($data: SocialLinkInput!) {
|
||||||
|
createSocialLink(data: $data) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
3
frontend/src/graphql/mutations/user/DisableTotp.graphql
Normal file
3
frontend/src/graphql/mutations/user/DisableTotp.graphql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mutation DisableTotp {
|
||||||
|
disableTotp
|
||||||
|
}
|
||||||
3
frontend/src/graphql/mutations/user/EnableTotp.graphql
Normal file
3
frontend/src/graphql/mutations/user/EnableTotp.graphql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mutation EnableTotp($data: EnableTotpInput!) {
|
||||||
|
enableTotp(data: $data)
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
mutation RemoveProfileAvatar {
|
||||||
|
removeProfileAvatar
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
mutation RemoveSession($id: String!) {
|
||||||
|
removeSession(id: $id)
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
mutation RemoveSocialLink($id: String!) {
|
||||||
|
removeSocialLink(id: $id)
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
mutation ReorderSocialLinks($list: [SocialLinkOrderInput!]!) {
|
||||||
|
reorderSocialLink(list: $list)
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
mutation UpdateSocialLink($id: String!, $data: SocialLinkInput!) {
|
||||||
|
updateSocialLink(id: $id, data: $data) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
19
frontend/src/graphql/queries/user/FindCurrentSession.graphql
Normal file
19
frontend/src/graphql/queries/user/FindCurrentSession.graphql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
query FindCurrentSession {
|
||||||
|
findCurrentSession {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
metadata {
|
||||||
|
location {
|
||||||
|
country
|
||||||
|
city
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
}
|
||||||
|
device {
|
||||||
|
browser
|
||||||
|
os
|
||||||
|
}
|
||||||
|
ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,5 +16,9 @@ query FindProfile {
|
|||||||
url
|
url
|
||||||
position
|
position
|
||||||
}
|
}
|
||||||
|
notificationSettings {
|
||||||
|
siteNotifications
|
||||||
|
telegramNotifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
frontend/src/graphql/queries/user/FindSessionsByUser.graphql
Normal file
19
frontend/src/graphql/queries/user/FindSessionsByUser.graphql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
query FindSessionsByUser {
|
||||||
|
findSessionsByUser {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
metadata {
|
||||||
|
location {
|
||||||
|
country
|
||||||
|
city
|
||||||
|
latitude
|
||||||
|
longitude
|
||||||
|
}
|
||||||
|
device {
|
||||||
|
browser
|
||||||
|
os
|
||||||
|
}
|
||||||
|
ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
query FindSocialLinks {
|
||||||
|
findSocialLinks {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
url
|
||||||
|
position
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
query GenerateTotpSecret {
|
||||||
|
generateTotpSecret {
|
||||||
|
qrcodeUrl
|
||||||
|
secret
|
||||||
|
}
|
||||||
|
}
|
||||||
11
frontend/src/hooks/useConfig.ts
Normal file
11
frontend/src/hooks/useConfig.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { configStore } from '@/store/config/config.store';
|
||||||
|
|
||||||
|
export function useConfig() {
|
||||||
|
const theme = configStore((state) => state.theme);
|
||||||
|
const setTheme = configStore((state) => state.setTheme);
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
setTheme,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,13 +1,39 @@
|
|||||||
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
|
import { ApolloClient, InMemoryCache, split } from '@apollo/client';
|
||||||
|
import { WebSocketLink } from '@apollo/client/link/ws';
|
||||||
|
import { getMainDefinition } from '@apollo/client/utilities';
|
||||||
|
import createUploadLink from 'apollo-upload-client/createUploadLink.mjs';
|
||||||
|
|
||||||
import { SERVER_URL } from './constants/url.constants';
|
import { SERVER_URL, WEBSOCKET_URL } from './constants/url.constants';
|
||||||
|
|
||||||
const httpLink = createHttpLink({
|
const httpLink = createUploadLink({
|
||||||
uri: SERVER_URL,
|
uri: SERVER_URL,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
// headers: {
|
||||||
|
// 'apollo-require-preflight': 'true',
|
||||||
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const wsLink = new WebSocketLink({
|
||||||
|
uri: WEBSOCKET_URL,
|
||||||
|
options: {
|
||||||
|
reconnect: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const splitLink = split(
|
||||||
|
({ query }) => {
|
||||||
|
const definition = getMainDefinition(query);
|
||||||
|
|
||||||
|
return (
|
||||||
|
definition.kind === 'OperationDefinition'
|
||||||
|
&& definition.operation === 'subscription'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
wsLink,
|
||||||
|
httpLink,
|
||||||
|
);
|
||||||
|
|
||||||
export const client = new ApolloClient({
|
export const client = new ApolloClient({
|
||||||
link: httpLink,
|
link: splitLink,
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
});
|
});
|
||||||
|
|||||||
36
frontend/src/libs/constants/colors.constants.ts
Normal file
36
frontend/src/libs/constants/colors.constants.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
export const BASE_COLORS = [
|
||||||
|
{
|
||||||
|
name: 'violet',
|
||||||
|
color: '262.1 83.3% 57.8%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'blue',
|
||||||
|
color: '204, 70%, 53%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'turquoise',
|
||||||
|
color: '176, 77%, 41%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'yellow',
|
||||||
|
color: '48, 89%, 50%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'peach',
|
||||||
|
color: '17, 94%, 67%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pink',
|
||||||
|
color: '330.4 81.2% 60.4%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rose',
|
||||||
|
color: '340, 82%, 52%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'red',
|
||||||
|
color: '0 72.2% 50.6%',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type TypeBaseColor = (typeof BASE_COLORS)[number]['name'];
|
||||||
15
frontend/src/libs/constants/seo.constants.ts
Normal file
15
frontend/src/libs/constants/seo.constants.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const NO_INDEX_PAGE = { robots: { index: false, follow: false } };
|
||||||
|
|
||||||
|
export const SITE_NAME = 'TeaStream';
|
||||||
|
|
||||||
|
export const SITE_DESCRIPTION = 'TeaStream — это платформа для прямых трансляций, которая соединяет стримеров и зрителей. Делитесь своими увлечениями и присоединяйтесь к сообществу TeaStream!';
|
||||||
|
|
||||||
|
export const SITE_KEYWORDS = [
|
||||||
|
'TeaStream',
|
||||||
|
'платформа для прямых трансляций',
|
||||||
|
'стриминг',
|
||||||
|
'прямые трансляции',
|
||||||
|
'взаимодействие со зрителями',
|
||||||
|
'видео-контент',
|
||||||
|
'онлайн-сообщество',
|
||||||
|
];
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL as string;
|
export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL as string;
|
||||||
|
export const WEBSOCKET_URL = process.env.NEXT_PUBLIC_WEBSOCKET_URL as string
|
||||||
export const MEDIA_URL = process.env.NEXT_PUBLIC_MEDIA_URL as string;
|
export const MEDIA_URL = process.env.NEXT_PUBLIC_MEDIA_URL as string;
|
||||||
|
|||||||
9
frontend/src/schemas/auth/deactivate.schema.ts
Normal file
9
frontend/src/schemas/auth/deactivate.schema.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const deactivateSchema = z.object({
|
||||||
|
email: z.email(),
|
||||||
|
password: z.string().min(8),
|
||||||
|
pin: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeDeactivateSchema = z.infer<typeof deactivateSchema>;
|
||||||
14
frontend/src/schemas/upload-file.schema.ts
Normal file
14
frontend/src/schemas/upload-file.schema.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
export const uploadFileSchema = z.object({
|
||||||
|
file: z
|
||||||
|
.union([
|
||||||
|
z.instanceof(File).refine((file) => file.size <= MAX_FILE_SIZE),
|
||||||
|
z.string().transform((value) => (value === '' ? undefined : value)),
|
||||||
|
])
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeUploadFileSchema = z.infer<typeof uploadFileSchema>;
|
||||||
7
frontend/src/schemas/user/change-email.schema.ts
Normal file
7
frontend/src/schemas/user/change-email.schema.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const changeEmailSchema = z.object({
|
||||||
|
email: z.email(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeChangeEmailSchema = z.infer<typeof changeEmailSchema>;
|
||||||
12
frontend/src/schemas/user/change-info.schema.ts
Normal file
12
frontend/src/schemas/user/change-info.schema.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const changeInfoSchema = z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.regex(/^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$/),
|
||||||
|
displayName: z.string().min(1),
|
||||||
|
bio: z.string().max(300),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeChangeInfoSchema = z.infer<typeof changeInfoSchema>;
|
||||||
9
frontend/src/schemas/user/change-language.schema.ts
Normal file
9
frontend/src/schemas/user/change-language.schema.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { languages } from '@/libs/i18n/config';
|
||||||
|
|
||||||
|
export const changeLanguageSchema = z.object({
|
||||||
|
language: z.enum(languages),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeChangeLanguageSchema = z.infer<typeof changeLanguageSchema>;
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const changeNotificationsSettingsSchema = z.object({
|
||||||
|
siteNotifications: z.boolean(),
|
||||||
|
telegramNotifications: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeChangeNotificationsSettingsSchema = z.infer<
|
||||||
|
typeof changeNotificationsSettingsSchema
|
||||||
|
>;
|
||||||
8
frontend/src/schemas/user/change-password.schema.ts
Normal file
8
frontend/src/schemas/user/change-password.schema.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const changePasswordSchema = z.object({
|
||||||
|
oldPassword: z.string().min(8),
|
||||||
|
newPassword: z.string().min(8),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeChangePasswordSchema = z.infer<typeof changePasswordSchema>;
|
||||||
7
frontend/src/schemas/user/change-theme.schema.ts
Normal file
7
frontend/src/schemas/user/change-theme.schema.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const changeThemeSchema = z.object({
|
||||||
|
theme: z.enum(['light', 'dark']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeChangeThemeSchema = z.infer<typeof changeThemeSchema>;
|
||||||
7
frontend/src/schemas/user/enable-totp.schema.ts
Normal file
7
frontend/src/schemas/user/enable-totp.schema.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const enableTotpSchema = z.object({
|
||||||
|
pin: z.string().length(6),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeEnableTotpSchema = z.infer<typeof enableTotpSchema>;
|
||||||
8
frontend/src/schemas/user/social-links.schema.ts
Normal file
8
frontend/src/schemas/user/social-links.schema.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const socialLinksSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
url: z.url(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TypeSocialLinksSchema = z.infer<typeof socialLinksSchema>;
|
||||||
18
frontend/src/store/config/config.store.ts
Normal file
18
frontend/src/store/config/config.store.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
import type { ConfigStore } from './config.types';
|
||||||
|
import type { TypeBaseColor } from '@/libs/constants/colors.constants';
|
||||||
|
|
||||||
|
export const configStore = create(
|
||||||
|
persist<ConfigStore>(
|
||||||
|
(set) => ({
|
||||||
|
theme: 'pink',
|
||||||
|
setTheme: (theme: TypeBaseColor) => { set({ theme }); },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'config',
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
6
frontend/src/store/config/config.types.ts
Normal file
6
frontend/src/store/config/config.types.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { TypeBaseColor } from '@/libs/constants/colors.constants';
|
||||||
|
|
||||||
|
export type ConfigStore = {
|
||||||
|
theme: TypeBaseColor;
|
||||||
|
setTheme: (theme: TypeBaseColor) => void;
|
||||||
|
};
|
||||||
113
frontend/src/styles/themes.css
Normal file
113
frontend/src/styles/themes.css
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
.theme-violet {
|
||||||
|
--primary: 262.1, 83.3%, 57.8%;
|
||||||
|
--primary-foreground: 210, 20%, 98%;
|
||||||
|
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-violet {
|
||||||
|
--primary: 263.4, 70%, 50.4%;
|
||||||
|
--primary-foreground: 210, 20%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-blue {
|
||||||
|
--primary: 204, 70%, 53%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-blue {
|
||||||
|
--primary: 204, 70%, 53%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-turquoise {
|
||||||
|
--primary: 176, 77%, 41%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-turquoise {
|
||||||
|
--primary: 176, 77%, 41%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-yellow {
|
||||||
|
--primary: 48, 89%, 50%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-yellow {
|
||||||
|
--primary: 48, 89%, 50%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-peach {
|
||||||
|
--primary: 17, 94%, 67%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-peach {
|
||||||
|
--primary: 17, 94%, 67%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-pink {
|
||||||
|
--primary: 330.4, 81.2%, 60.4%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-pink {
|
||||||
|
--primary: 330.4, 81.2%, 60.4%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-rose {
|
||||||
|
--primary: 340, 82%, 52%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-rose {
|
||||||
|
--primary: 340, 82%, 52%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-red {
|
||||||
|
--primary: 0, 72.2%, 50.6%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .theme-red {
|
||||||
|
--primary: 0, 72.2%, 50.6%;
|
||||||
|
--primary-foreground: 210, 40%, 98%;
|
||||||
|
--color-primary: hsl(var(--primary));
|
||||||
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
||||||
|
}
|
||||||
|
|
||||||
40
frontend/src/utils/format-date.ts
Normal file
40
frontend/src/utils/format-date.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
export function formatDate(
|
||||||
|
dateString: Date | string,
|
||||||
|
includeTime = false,
|
||||||
|
) {
|
||||||
|
const t = useTranslations('utils.formatDate');
|
||||||
|
|
||||||
|
const date = new Date(dateString);
|
||||||
|
|
||||||
|
const day = date.getDate();
|
||||||
|
const monthIndex = date.getMonth();
|
||||||
|
const year = date.getFullYear();
|
||||||
|
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
const months = [
|
||||||
|
t('months.january'),
|
||||||
|
t('months.february'),
|
||||||
|
t('months.march'),
|
||||||
|
t('months.april'),
|
||||||
|
t('months.may'),
|
||||||
|
t('months.june'),
|
||||||
|
t('months.july'),
|
||||||
|
t('months.august'),
|
||||||
|
t('months.september'),
|
||||||
|
t('months.october'),
|
||||||
|
t('months.november'),
|
||||||
|
t('months.december'),
|
||||||
|
];
|
||||||
|
|
||||||
|
let formattedDate = `${day} ${months[monthIndex]} ${year}`;
|
||||||
|
|
||||||
|
if (includeTime) {
|
||||||
|
formattedDate += `, ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedDate;
|
||||||
|
}
|
||||||
32
frontend/src/utils/get-browser-icon.ts
Normal file
32
frontend/src/utils/get-browser-icon.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { CircleHelp } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
FaChrome,
|
||||||
|
FaEdge,
|
||||||
|
FaFirefoxBrowser,
|
||||||
|
FaOpera,
|
||||||
|
FaSafari,
|
||||||
|
FaYandex,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
|
||||||
|
export function getBrowserIcon(browser: string) {
|
||||||
|
switch (browser.toLowerCase()) {
|
||||||
|
case 'chrome':
|
||||||
|
return FaChrome;
|
||||||
|
case 'firefox':
|
||||||
|
return FaFirefoxBrowser;
|
||||||
|
case 'safari':
|
||||||
|
return FaSafari;
|
||||||
|
case 'edge':
|
||||||
|
return FaEdge;
|
||||||
|
case 'microsoft edge':
|
||||||
|
return FaEdge;
|
||||||
|
case 'opera':
|
||||||
|
return FaOpera;
|
||||||
|
case 'yandex':
|
||||||
|
return FaYandex;
|
||||||
|
case 'yandex browser':
|
||||||
|
return FaYandex;
|
||||||
|
default:
|
||||||
|
return CircleHelp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { Config } from 'tailwindcss'
|
import type { Config } from 'tailwindcss';
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||||
@ -7,68 +7,68 @@ const config: Config = {
|
|||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
|
||||||
'gradient-conic':
|
'gradient-conic':
|
||||||
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
|
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
sm: 'calc(var(--radius) - 4px)',
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['var(--font-geist-sans)']
|
sans: ['var(--font-geist-sans)'],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--foreground))',
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
foreground: 'hsl(var(--card-foreground))',
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
foreground: 'hsl(var(--popover-foreground))',
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
foreground: 'hsl(var(--primary-foreground))',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
foreground: 'hsl(var(--secondary-foreground))',
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
foreground: 'hsl(var(--muted-foreground))',
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
foreground: 'hsl(var(--accent-foreground))',
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
foreground: 'hsl(var(--destructive-foreground))',
|
||||||
},
|
},
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
chart: {
|
chart: {
|
||||||
'1': 'hsl(var(--chart-1))',
|
1: 'hsl(var(--chart-1))',
|
||||||
'2': 'hsl(var(--chart-2))',
|
2: 'hsl(var(--chart-2))',
|
||||||
'3': 'hsl(var(--chart-3))',
|
3: 'hsl(var(--chart-3))',
|
||||||
'4': 'hsl(var(--chart-4))',
|
4: 'hsl(var(--chart-4))',
|
||||||
'5': 'hsl(var(--chart-5))'
|
5: 'hsl(var(--chart-5))',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
'caret-blink': {
|
'caret-blink': {
|
||||||
'0%,70%,100%': { opacity: '1' },
|
'0%,70%,100%': { opacity: '1' },
|
||||||
'20%,50%': { opacity: '0' }
|
'20%,50%': { opacity: '0' },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
'caret-blink': 'caret-blink 1.25s ease-out infinite'
|
'caret-blink': 'caret-blink 1.25s ease-out infinite',
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
export default config
|
},
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
|
|||||||
@ -451,7 +451,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.27.1"
|
"@babel/helper-plugin-utils" "^7.27.1"
|
||||||
|
|
||||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.26.10":
|
"@babel/runtime@^7.0.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.26.10", "@babel/runtime@^7.26.7":
|
||||||
version "7.28.2"
|
version "7.28.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.2.tgz#2ae5a9d51cc583bd1f5673b3bb70d6d819682473"
|
||||||
integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==
|
integrity sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==
|
||||||
@ -1214,6 +1214,17 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
||||||
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
||||||
|
|
||||||
|
"@hello-pangea/dnd@18.0.1":
|
||||||
|
version "18.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-18.0.1.tgz#7d5ef7fe8bddf195307b16e03635b1be582b7b8d"
|
||||||
|
integrity sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.26.7"
|
||||||
|
css-box-model "^1.2.1"
|
||||||
|
raf-schd "^4.0.3"
|
||||||
|
react-redux "^9.2.0"
|
||||||
|
redux "^5.0.1"
|
||||||
|
|
||||||
"@hookform/resolvers@5.2.1":
|
"@hookform/resolvers@5.2.1":
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-5.2.1.tgz#3332b4662fe301a969ac1b795d663cf728329166"
|
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-5.2.1.tgz#3332b4662fe301a969ac1b795d663cf728329166"
|
||||||
@ -1603,11 +1614,35 @@
|
|||||||
"@parcel/watcher-win32-ia32" "2.5.1"
|
"@parcel/watcher-win32-ia32" "2.5.1"
|
||||||
"@parcel/watcher-win32-x64" "2.5.1"
|
"@parcel/watcher-win32-x64" "2.5.1"
|
||||||
|
|
||||||
|
"@pbe/react-yandex-maps@1.2.5":
|
||||||
|
version "1.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@pbe/react-yandex-maps/-/react-yandex-maps-1.2.5.tgz#96c9781b2cb1f8ccc6d1650e8384afa38667c4ac"
|
||||||
|
integrity sha512-cBojin5e1fPx9XVCAqHQJsCnHGMeBNsP0TrNfpWCrPFfxb30ye+JgcGr2mn767Gbr1d+RufBLRiUcX2kaiAwjQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/yandex-maps" "2.1.29"
|
||||||
|
|
||||||
|
"@radix-ui/number@1.1.1":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.1.tgz#7b2c9225fbf1b126539551f5985769d0048d9090"
|
||||||
|
integrity sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==
|
||||||
|
|
||||||
"@radix-ui/primitive@1.1.2":
|
"@radix-ui/primitive@1.1.2":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.2.tgz#83f415c4425f21e3d27914c12b3272a32e3dae65"
|
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.2.tgz#83f415c4425f21e3d27914c12b3272a32e3dae65"
|
||||||
integrity sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==
|
integrity sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==
|
||||||
|
|
||||||
|
"@radix-ui/react-alert-dialog@1.1.14":
|
||||||
|
version "1.1.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.14.tgz#b38853b6859b9c7351d9aa52dd504a6bb2ea283c"
|
||||||
|
integrity sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive" "1.1.2"
|
||||||
|
"@radix-ui/react-compose-refs" "1.1.2"
|
||||||
|
"@radix-ui/react-context" "1.1.2"
|
||||||
|
"@radix-ui/react-dialog" "1.1.14"
|
||||||
|
"@radix-ui/react-primitive" "2.1.3"
|
||||||
|
"@radix-ui/react-slot" "1.2.3"
|
||||||
|
|
||||||
"@radix-ui/react-arrow@1.1.7":
|
"@radix-ui/react-arrow@1.1.7":
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09"
|
||||||
@ -1646,6 +1681,26 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
|
||||||
integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
|
integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
|
||||||
|
|
||||||
|
"@radix-ui/react-dialog@1.1.14":
|
||||||
|
version "1.1.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz#4c69c80c258bc6561398cfce055202ea11075107"
|
||||||
|
integrity sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive" "1.1.2"
|
||||||
|
"@radix-ui/react-compose-refs" "1.1.2"
|
||||||
|
"@radix-ui/react-context" "1.1.2"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.1.10"
|
||||||
|
"@radix-ui/react-focus-guards" "1.1.2"
|
||||||
|
"@radix-ui/react-focus-scope" "1.1.7"
|
||||||
|
"@radix-ui/react-id" "1.1.1"
|
||||||
|
"@radix-ui/react-portal" "1.1.9"
|
||||||
|
"@radix-ui/react-presence" "1.1.4"
|
||||||
|
"@radix-ui/react-primitive" "2.1.3"
|
||||||
|
"@radix-ui/react-slot" "1.2.3"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||||
|
aria-hidden "^1.2.4"
|
||||||
|
react-remove-scroll "^2.6.3"
|
||||||
|
|
||||||
"@radix-ui/react-direction@1.1.1":
|
"@radix-ui/react-direction@1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz#39e5a5769e676c753204b792fbe6cf508e550a14"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.1.tgz#39e5a5769e676c753204b792fbe6cf508e550a14"
|
||||||
@ -1802,6 +1857,33 @@
|
|||||||
"@radix-ui/react-use-callback-ref" "1.1.1"
|
"@radix-ui/react-use-callback-ref" "1.1.1"
|
||||||
"@radix-ui/react-use-controllable-state" "1.2.2"
|
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||||
|
|
||||||
|
"@radix-ui/react-select@2.2.5":
|
||||||
|
version "2.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.2.5.tgz#9e2fa5b8f4cc99b86ef5bba3cb9b73828afb51f0"
|
||||||
|
integrity sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/number" "1.1.1"
|
||||||
|
"@radix-ui/primitive" "1.1.2"
|
||||||
|
"@radix-ui/react-collection" "1.1.7"
|
||||||
|
"@radix-ui/react-compose-refs" "1.1.2"
|
||||||
|
"@radix-ui/react-context" "1.1.2"
|
||||||
|
"@radix-ui/react-direction" "1.1.1"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.1.10"
|
||||||
|
"@radix-ui/react-focus-guards" "1.1.2"
|
||||||
|
"@radix-ui/react-focus-scope" "1.1.7"
|
||||||
|
"@radix-ui/react-id" "1.1.1"
|
||||||
|
"@radix-ui/react-popper" "1.2.7"
|
||||||
|
"@radix-ui/react-portal" "1.1.9"
|
||||||
|
"@radix-ui/react-primitive" "2.1.3"
|
||||||
|
"@radix-ui/react-slot" "1.2.3"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.1.1"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.1.1"
|
||||||
|
"@radix-ui/react-use-previous" "1.1.1"
|
||||||
|
"@radix-ui/react-visually-hidden" "1.2.3"
|
||||||
|
aria-hidden "^1.2.4"
|
||||||
|
react-remove-scroll "^2.6.3"
|
||||||
|
|
||||||
"@radix-ui/react-separator@^1.1.7":
|
"@radix-ui/react-separator@^1.1.7":
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.7.tgz#a18bd7fd07c10fda1bba14f2a3032e7b1a2b3470"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.7.tgz#a18bd7fd07c10fda1bba14f2a3032e7b1a2b3470"
|
||||||
@ -1816,6 +1898,33 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@radix-ui/react-compose-refs" "1.1.2"
|
"@radix-ui/react-compose-refs" "1.1.2"
|
||||||
|
|
||||||
|
"@radix-ui/react-switch@1.2.5":
|
||||||
|
version "1.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.2.5.tgz#56c15a4cd219e00b0745ec6b2ea1c0feeb0b21d0"
|
||||||
|
integrity sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive" "1.1.2"
|
||||||
|
"@radix-ui/react-compose-refs" "1.1.2"
|
||||||
|
"@radix-ui/react-context" "1.1.2"
|
||||||
|
"@radix-ui/react-primitive" "2.1.3"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||||
|
"@radix-ui/react-use-previous" "1.1.1"
|
||||||
|
"@radix-ui/react-use-size" "1.1.1"
|
||||||
|
|
||||||
|
"@radix-ui/react-tabs@^1.1.12":
|
||||||
|
version "1.1.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz#99b3522c73db9263f429a6d0f5a9acb88df3b129"
|
||||||
|
integrity sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive" "1.1.2"
|
||||||
|
"@radix-ui/react-context" "1.1.2"
|
||||||
|
"@radix-ui/react-direction" "1.1.1"
|
||||||
|
"@radix-ui/react-id" "1.1.1"
|
||||||
|
"@radix-ui/react-presence" "1.1.4"
|
||||||
|
"@radix-ui/react-primitive" "2.1.3"
|
||||||
|
"@radix-ui/react-roving-focus" "1.1.10"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||||
|
|
||||||
"@radix-ui/react-tooltip@1.2.7":
|
"@radix-ui/react-tooltip@1.2.7":
|
||||||
version "1.2.7"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz#23612ac7a5e8e1f6829e46d0e0ad94afe3976c72"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz#23612ac7a5e8e1f6829e46d0e0ad94afe3976c72"
|
||||||
@ -1873,6 +1982,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e"
|
||||||
integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
|
integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
|
||||||
|
|
||||||
|
"@radix-ui/react-use-previous@1.1.1":
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz#1a1ad5568973d24051ed0af687766f6c7cb9b5b5"
|
||||||
|
integrity sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==
|
||||||
|
|
||||||
"@radix-ui/react-use-rect@1.1.1":
|
"@radix-ui/react-use-rect@1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152"
|
||||||
@ -2145,6 +2259,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/use-sync-external-store@^0.0.6":
|
||||||
|
version "0.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc"
|
||||||
|
integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==
|
||||||
|
|
||||||
"@types/ws@^8.0.0":
|
"@types/ws@^8.0.0":
|
||||||
version "8.18.1"
|
version "8.18.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
|
||||||
@ -2152,6 +2271,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/yandex-maps@2.1.29":
|
||||||
|
version "2.1.29"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/yandex-maps/-/yandex-maps-2.1.29.tgz#88dba8d99a4a05c6bf6d7fbc36c78e891ef30d38"
|
||||||
|
integrity sha512-nuibRWj3RU/9KXlCzTrRtDE+n6V9l7NbT9JakicqZ5OXIdwyb6blvV2Uwn6lB58WYm3DSUDP2I2AWlnWMc8z2w==
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@8.37.0":
|
"@typescript-eslint/eslint-plugin@8.37.0":
|
||||||
version "8.37.0"
|
version "8.37.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz#332392883f936137cd6252c8eb236d298e514e70"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz#332392883f936137cd6252c8eb236d298e514e70"
|
||||||
@ -2565,6 +2689,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
|
|
||||||
|
apollo-upload-client@18.0.1:
|
||||||
|
version "18.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-18.0.1.tgz#e3811f2f5a36bffef23954f796daf331be748dcb"
|
||||||
|
integrity sha512-OQvZg1rK05VNI79D658FUmMdoI2oB/KJKb6QGMa2Si25QXOaAvLMBFUEwJct7wf+19U8vk9ILhidBOU1ZWv6QA==
|
||||||
|
dependencies:
|
||||||
|
extract-files "^13.0.0"
|
||||||
|
|
||||||
arg@^4.1.0:
|
arg@^4.1.0:
|
||||||
version "4.1.3"
|
version "4.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||||
@ -2763,6 +2894,11 @@ babel-preset-fbjs@^3.4.0:
|
|||||||
"@babel/plugin-transform-template-literals" "^7.0.0"
|
"@babel/plugin-transform-template-literals" "^7.0.0"
|
||||||
babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0"
|
babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0"
|
||||||
|
|
||||||
|
backo2@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
|
||||||
|
integrity sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
@ -3104,6 +3240,13 @@ cross-spawn@^7.0.6:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
|
css-box-model@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
|
||||||
|
integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
|
||||||
|
dependencies:
|
||||||
|
tiny-invariant "^1.0.6"
|
||||||
|
|
||||||
csstype@^3.0.2:
|
csstype@^3.0.2:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||||
@ -3771,6 +3914,11 @@ esutils@^2.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||||
|
|
||||||
|
eventemitter3@^3.1.0:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
|
||||||
|
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
|
||||||
|
|
||||||
external-editor@^3.0.3:
|
external-editor@^3.0.3:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
|
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
|
||||||
@ -3780,6 +3928,13 @@ external-editor@^3.0.3:
|
|||||||
iconv-lite "^0.4.24"
|
iconv-lite "^0.4.24"
|
||||||
tmp "^0.0.33"
|
tmp "^0.0.33"
|
||||||
|
|
||||||
|
extract-files@^13.0.0:
|
||||||
|
version "13.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-13.0.0.tgz#9065073dedbcfb5e2ae8a90988cf609834b217ec"
|
||||||
|
integrity sha512-FXD+2Tsr8Iqtm3QZy1Zmwscca7Jx3mMC5Crr+sEP1I303Jy1CYMuYCm7hRTplFNg3XdUavErkxnTzpaqdSoi6g==
|
||||||
|
dependencies:
|
||||||
|
is-plain-obj "^4.1.0"
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
@ -4501,6 +4656,11 @@ is-number@^7.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||||
|
|
||||||
|
is-plain-obj@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
|
||||||
|
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
|
||||||
|
|
||||||
is-regex@^1.2.1:
|
is-regex@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
|
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
|
||||||
@ -4613,6 +4773,11 @@ isomorphic-ws@^5.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf"
|
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf"
|
||||||
integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==
|
integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==
|
||||||
|
|
||||||
|
iterall@^1.2.1:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
|
||||||
|
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
|
||||||
|
|
||||||
iterator.prototype@^1.1.4:
|
iterator.prototype@^1.1.4:
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39"
|
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39"
|
||||||
@ -5430,6 +5595,11 @@ queue-microtask@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
raf-schd@^4.0.3:
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||||
|
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
||||||
|
|
||||||
react-dom@19.1.1:
|
react-dom@19.1.1:
|
||||||
version "19.1.1"
|
version "19.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893"
|
||||||
@ -5442,6 +5612,11 @@ react-hook-form@7.61.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.61.1.tgz#8c1f086ccd921a6e90df6800787e9ca3833f86c1"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.61.1.tgz#8c1f086ccd921a6e90df6800787e9ca3833f86c1"
|
||||||
integrity sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==
|
integrity sha512-2vbXUFDYgqEgM2RcXcAT2PwDW/80QARi+PKmHy5q2KhuKvOlG8iIYgf7eIlIANR5trW9fJbP4r5aub3a4egsew==
|
||||||
|
|
||||||
|
react-icons@5.5.0:
|
||||||
|
version "5.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.5.0.tgz#8aa25d3543ff84231685d3331164c00299cdfaf2"
|
||||||
|
integrity sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@ -5452,6 +5627,14 @@ react-property@2.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
|
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
|
||||||
integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
|
integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
|
||||||
|
|
||||||
|
react-redux@^9.2.0:
|
||||||
|
version "9.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5"
|
||||||
|
integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==
|
||||||
|
dependencies:
|
||||||
|
"@types/use-sync-external-store" "^0.0.6"
|
||||||
|
use-sync-external-store "^1.4.0"
|
||||||
|
|
||||||
react-remove-scroll-bar@^2.3.7:
|
react-remove-scroll-bar@^2.3.7:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223"
|
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz#99c20f908ee467b385b68a3469b4a3e750012223"
|
||||||
@ -5493,6 +5676,11 @@ readable-stream@^3.4.0:
|
|||||||
string_decoder "^1.1.1"
|
string_decoder "^1.1.1"
|
||||||
util-deprecate "^1.0.1"
|
util-deprecate "^1.0.1"
|
||||||
|
|
||||||
|
redux@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
|
||||||
|
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
|
||||||
|
|
||||||
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
|
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
|
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
|
||||||
@ -6040,6 +6228,17 @@ styled-jsx@5.1.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
client-only "0.0.1"
|
client-only "0.0.1"
|
||||||
|
|
||||||
|
subscriptions-transport-ws@0.11.0:
|
||||||
|
version "0.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.11.0.tgz#baf88f050cba51d52afe781de5e81b3c31f89883"
|
||||||
|
integrity sha512-8D4C6DIH5tGiAIpp5I0wD/xRlNiZAPGHygzCe7VzyzUoxHtawzjNAY9SUTXU05/EY2NMY9/9GF0ycizkXr1CWQ==
|
||||||
|
dependencies:
|
||||||
|
backo2 "^1.0.2"
|
||||||
|
eventemitter3 "^3.1.0"
|
||||||
|
iterall "^1.2.1"
|
||||||
|
symbol-observable "^1.0.4"
|
||||||
|
ws "^5.2.0 || ^6.0.0 || ^7.0.0"
|
||||||
|
|
||||||
supports-color@^7.1.0:
|
supports-color@^7.1.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||||
@ -6059,6 +6258,11 @@ swap-case@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
|
symbol-observable@^1.0.4:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
|
||||||
|
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
|
||||||
|
|
||||||
symbol-observable@^4.0.0:
|
symbol-observable@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"
|
||||||
@ -6110,6 +6314,11 @@ timeout-signal@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/timeout-signal/-/timeout-signal-2.0.0.tgz#23207ea448d50258bb0defe3beea4a467643abba"
|
resolved "https://registry.yarnpkg.com/timeout-signal/-/timeout-signal-2.0.0.tgz#23207ea448d50258bb0defe3beea4a467643abba"
|
||||||
integrity sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==
|
integrity sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA==
|
||||||
|
|
||||||
|
tiny-invariant@^1.0.6:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
||||||
|
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
||||||
|
|
||||||
tinyglobby@^0.2.13, tinyglobby@^0.2.14:
|
tinyglobby@^0.2.13, tinyglobby@^0.2.14:
|
||||||
version "0.2.14"
|
version "0.2.14"
|
||||||
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
|
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
|
||||||
@ -6404,7 +6613,7 @@ use-sidecar@^1.1.3:
|
|||||||
detect-node-es "^1.1.0"
|
detect-node-es "^1.1.0"
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
|
|
||||||
use-sync-external-store@^1.5.0:
|
use-sync-external-store@^1.4.0, use-sync-external-store@^1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"
|
||||||
integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
|
integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
|
||||||
@ -6542,6 +6751,11 @@ wrappy@1:
|
|||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||||
|
|
||||||
|
"ws@^5.2.0 || ^6.0.0 || ^7.0.0":
|
||||||
|
version "7.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
|
||||||
|
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
||||||
|
|
||||||
ws@^8.17.1, ws@^8.18.3:
|
ws@^8.17.1, ws@^8.18.3:
|
||||||
version "8.18.3"
|
version "8.18.3"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user