Compare commits

...

36 Commits

Author SHA1 Message Date
vchikalkin
3fb25cd667 update name on telegram first login 2025-01-20 18:06:43 +03:00
vchikalkin
c2ca2f9df5 telegram login: customer.active=true 2025-01-20 18:03:13 +03:00
vchikalkin
d15f42de1a contacts: loading spinner 2025-01-20 17:29:05 +03:00
vchikalkin
21b2e983eb do not open keyboard on page load 2025-01-15 18:38:24 +03:00
vchikalkin
30b0d4d394 app/bot: normalize phone before register 2025-01-15 18:31:28 +03:00
vchikalkin
d987c98b30 profile: add call feature 2025-01-15 18:20:56 +03:00
vchikalkin
53c74d565f profile: add button "message to telegram" 2025-01-15 18:15:04 +03:00
vchikalkin
2b1a499167 add page 'profile/[telegramId]' 2025-01-15 18:03:31 +03:00
vchikalkin
d00f8475f2 beautify actions 2025-01-15 17:23:17 +03:00
vchikalkin
7652ca9bb4 move updateRole closer to profile-card 2025-01-15 17:13:22 +03:00
vchikalkin
0a49f8dc7d refactor updateRole function 2025-01-15 17:00:50 +03:00
vchikalkin
248fdd0e41 profile: use react-query 2025-01-15 15:19:58 +03:00
vchikalkin
4af2ae4d4e replace zustand with @tanstack/react-query 2025-01-15 14:37:58 +03:00
vchikalkin
59e9d61e37 rename filteredContacts -> contacts 2025-01-14 19:32:50 +03:00
vchikalkin
f6c52c11c3 add loading spinner 2025-01-14 19:29:54 +03:00
vchikalkin
000cb77acd use zustand for contacts 2025-01-14 19:10:05 +03:00
vchikalkin
550c5474a3 get customer contacts 2025-01-14 15:40:31 +03:00
vchikalkin
0a7d9c75c0 add action getProfile 2025-01-13 20:03:01 +03:00
vchikalkin
af128f42c5 refactor customer queries 2025-01-13 19:50:25 +03:00
vchikalkin
ab6211825e fix app layout 2025-01-13 17:31:29 +03:00
vchikalkin
e48f0eb951 feat(apps/web): add basic /contacts page 2025-01-13 16:58:47 +03:00
vchikalkin
38bb5193f9 rename page 'masters' -> 'contacts' 2025-01-12 18:22:57 +03:00
vchikalkin
56a299c656 feat(apps/web): update user photo on app launch 2025-01-12 18:19:29 +03:00
vchikalkin
f1f1ac3183 fix(apps/web): actions getCustomer 2025-01-12 16:04:10 +03:00
vchikalkin
e31953ba57 fix(apps/web): user is undefined 2025-01-12 15:52:10 +03:00
vchikalkin
a8f5f47293 tests for createOrUpdateClient 2025-01-12 15:49:03 +03:00
vchikalkin
8d9ed72b91 packages/graphql: add api/customer tests 2025-01-12 15:34:37 +03:00
vchikalkin
cd027f29e6 apps/bot: remove api.ts -> move getCustomer to packages/graphql/api 2025-01-12 12:40:17 +03:00
vchikalkin
f1e12a48aa apps/bot: create or update functions 2025-01-10 19:39:47 +03:00
vchikalkin
0d3b513b69 Чтобы добавить контакт, сначала поделитесь своим номером телефона. 2025-01-10 19:10:29 +03:00
vchikalkin
f1c98fd271 apps/bot: check user already exists w/o telegramId (invited) 2025-01-10 19:06:30 +03:00
vchikalkin
db1dcbf581 app/bot: add contact define name & phone 2025-01-10 18:51:55 +03:00
vchikalkin
87ec038739 remove ';' 2025-01-10 18:45:15 +03:00
vchikalkin
23f5838c82 apps/bot: rename createCustomer -> createUser 2025-01-10 18:44:14 +03:00
vchikalkin
b1321e751d apps/bot: check role 'master' before add contact 2025-01-10 18:40:58 +03:00
vchikalkin
d723758704 apps/bot: add feature add contact 2025-01-10 18:35:06 +03:00
34 changed files with 1546 additions and 157 deletions

View File

@ -1,61 +1,92 @@
/* eslint-disable canonical/id-match */
/* eslint-disable consistent-return */
import { env as environment } from './config/env';
import { createCustomer, getCustomer } from '@repo/graphql/api';
import { commandsList, KEYBOARD_REMOVE, KEYBOARD_SHARE_PHONE } from './message';
import { normalizePhoneNumber } from './utils/phone';
import { createOrUpdateUser, getCustomer, updateCustomerMaster } from '@repo/graphql/api';
import { Enum_Customer_Role } from '@repo/graphql/types';
import { Telegraf } from 'telegraf';
import { message } from 'telegraf/filters';
const bot = new Telegraf(environment.BOT_TOKEN);
bot.start(async (context) => {
const { id: telegramId } = context.from;
const customer = await getCustomer({ telegramId: context.from.id });
const response = await getCustomer({ telegramId });
const customer = response?.data?.customers?.at(0);
if (customer && !response.error) {
if (customer) {
return context.reply(
`Приветствуем снова, ${customer.name}. Чтобы воспользоваться сервисом, откройте приложение.`,
{
reply_markup: {
remove_keyboard: true,
},
},
`Приветствуем снова, ${customer.name} 👋.
Чтобы воспользоваться сервисом, откройте приложение.` + commandsList,
KEYBOARD_REMOVE,
);
}
return context.reply('Добро пожаловать! Пожалуйста, поделитесь своим номером телефона.', {
reply_markup: {
keyboard: [
[
{
request_contact: true,
text: 'Отправить номер телефона',
},
],
],
one_time_keyboard: true,
},
});
return context.reply(
'Добро пожаловать! Пожалуйста, поделитесь своим номером телефона.',
KEYBOARD_SHARE_PHONE,
);
});
bot.command('addcontact', async (context) => {
const customer = await getCustomer({ telegramId: context.from.id });
if (!customer) {
return context.reply(
'Чтобы добавить контакт, сначала поделитесь своим номером телефона.',
KEYBOARD_SHARE_PHONE,
);
}
return context.reply('Отправьте контакт клиента, которого вы хотите добавить');
});
bot.on(message('contact'), async (context) => {
const response = await createCustomer({
name: context.from.first_name,
phone: context.message.contact.phone_number,
telegramId: context.from.id,
});
const customer = await getCustomer({ telegramId: context.from.id });
const isRegistration = !customer;
if (response.errors?.length) {
return context.reply('Произошла ошибка, попробуйте позже.');
const { contact } = context.message;
const name = (contact.first_name || '') + ' ' + (contact.last_name || '').trim();
const phone = normalizePhoneNumber(contact.phone_number);
if (isRegistration) {
const response = await createOrUpdateUser({
name,
phone,
telegramId: context.from.id,
}).catch((error) => {
context.reply('Произошла ошибка.\n' + error);
});
if (response) {
return context.reply(
`Спасибо! Мы сохранили ваш номер телефона. Теперь можете открыть приложение или воспользоваться командами бота.` +
commandsList,
KEYBOARD_REMOVE,
);
}
} else {
if (customer.role !== Enum_Customer_Role.Master) {
return context.reply(
'Только мастер может добавлять контакты. \nСтать мастером можно на странице профиля в приложении.',
);
}
try {
await createOrUpdateUser({ name, phone });
await updateCustomerMaster({
masterId: customer.documentId,
operation: 'add',
phone,
});
return context.reply(
`Добавили контакт ${name}. Пригласите пользователя в приложение и тогда вы сможете добавлять записи с этим контактом.`,
);
} catch (error) {
context.reply('Произошла ошибка.\n' + error);
}
}
return context.reply(
'Спасибо! Мы сохранили ваш номер телефона. Теперь можете открыть приложение.',
{
reply_markup: {
remove_keyboard: true,
},
},
);
});
bot.launch();

26
apps/bot/src/message.ts Normal file
View File

@ -0,0 +1,26 @@
import { type ReplyKeyboardRemove } from 'telegraf/types';
export const commandsList = `
\оступные команды:
/addcontact - Добавить контакт клиента
`;
export const KEYBOARD_SHARE_PHONE = {
reply_markup: {
keyboard: [
[
{
request_contact: true,
text: 'Отправить номер телефона',
},
],
],
one_time_keyboard: true,
},
};
export const KEYBOARD_REMOVE = {
reply_markup: {
remove_keyboard: true,
} as ReplyKeyboardRemove,
};

View File

@ -0,0 +1,5 @@
export function normalizePhoneNumber(phone: string): string {
const digitsOnly = phone.replaceAll(/\D/gu, '');
return `+${digitsOnly}`;
}

View File

@ -0,0 +1,30 @@
'use server';
import { authOptions } from '@/config/auth';
import { getCustomerClients, getCustomerMasters } from '@repo/graphql/api';
import { getServerSession } from 'next-auth/next';
export async function getClients() {
const session = await getServerSession(authOptions);
if (!session) throw new Error('Missing session');
const { user } = session;
const getCustomerClientsResponse = await getCustomerClients({ telegramId: user?.telegramId });
return {
clients: getCustomerClientsResponse?.clients,
};
}
export async function getMasters() {
const session = await getServerSession(authOptions);
if (!session) throw new Error('Missing session');
const { user } = session;
const getCustomerMastersResponse = await getCustomerMasters({ telegramId: user?.telegramId });
return {
masters: getCustomerMastersResponse?.masters,
};
}

View File

@ -1,44 +1,35 @@
'use server';
import { authOptions } from '@/config/auth';
import { getCustomer, updateCustomerProfile } from '@repo/graphql/api';
import { type CustomerInput, type Enum_Customer_Role } from '@repo/graphql/types';
import { type CustomerInput, type GetCustomerQueryVariables } from '@repo/graphql/types';
import { getServerSession } from 'next-auth/next';
import { revalidatePath } from 'next/cache';
export async function getProfile(input?: GetCustomerQueryVariables) {
const session = await getServerSession(authOptions);
if (!session) throw new Error('Missing session');
const { user } = session;
const telegramId = input?.telegramId || user?.telegramId;
const customer = await getCustomer({ telegramId });
return customer;
}
export async function updateProfile(input: CustomerInput) {
const session = await getServerSession(authOptions);
if (!session) throw new Error('Missing session');
if (session) {
const { user } = session;
const getCustomerResponse = await getCustomer({ telegramId: user?.telegramId });
const customer = getCustomerResponse.data.customers.at(0);
if (customer) {
await updateCustomerProfile({
data: input,
documentId: customer.documentId,
});
}
}
revalidatePath('/profile');
}
export async function updateRole(role: Enum_Customer_Role) {
const session = await getServerSession(authOptions);
if (session) {
const { user } = session;
const getCustomerResponse = await getCustomer({ telegramId: user?.telegramId });
const customer = getCustomerResponse.data.customers.at(0);
if (customer) {
await updateCustomerProfile({
data: {
role,
},
documentId: customer.documentId,
});
}
}
const { user } = session;
const customer = await getCustomer({ telegramId: user?.telegramId });
if (!customer) throw new Error('Customer not found');
await updateCustomerProfile({
data: input,
documentId: customer.documentId,
});
revalidatePath('/profile');
}

View File

@ -1,4 +1,4 @@
import { TelegramProvider } from '@/providers';
import { TelegramProvider } from '@/providers/telegram';
import { type PropsWithChildren } from 'react';
export default async function Layout({ children }: Readonly<PropsWithChildren>) {

View File

@ -1,20 +1,52 @@
'use client';
import { getProfile, updateProfile } from '@/actions/profile';
import { LoadingSpinner } from '@repo/ui/components/ui/spinner';
import { initData, isMiniAppDark, useSignal } from '@telegram-apps/sdk-react';
import { signIn, useSession } from 'next-auth/react';
import { useTheme } from 'next-themes';
import { redirect } from 'next/navigation';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
export default function Auth() {
const initDataUser = useSignal(initData.user);
const isDark = isMiniAppDark();
const { status } = useSession();
const { setTheme } = useTheme();
const [isUpdating, setIsUpdating] = useState(true);
useEffect(() => {
setTheme(isDark ? 'dark' : 'light');
const update = async () => {
if (initDataUser?.photoUrl) {
await updateProfile({ photoUrl: initDataUser.photoUrl });
}
const customer = await getProfile({ telegramId: initDataUser?.id });
if (!customer?.active) {
await updateProfile({
active: true,
name: `${initDataUser?.firstName || ''} + ' ' + ${initDataUser?.lastName}`.trim(),
});
}
setIsUpdating(false);
};
update();
}, [
initDataUser?.firstName,
initDataUser?.id,
initDataUser?.lastName,
initDataUser?.photoUrl,
isDark,
setTheme,
]);
useEffect(() => {
if (isUpdating) return;
if (status === 'authenticated') {
redirect('/profile');
}
@ -26,7 +58,7 @@ export default function Auth() {
telegramId: String(initDataUser.id),
});
}
}, [initDataUser, isDark, setTheme, status]);
}, [initDataUser?.id, isUpdating, status]);
return <LoadingSpinner />;
}

View File

@ -0,0 +1,17 @@
import { ContactsList } from '@/components/contacts/contacts-list';
import { ContactsFilter } from '@/components/contacts/dropdown-filter';
import { ContactsFilterProvider } from '@/context/contacts-filter';
export default function ContactsPage() {
return (
<ContactsFilterProvider>
<div className="sticky top-0 z-50 flex flex-row items-center justify-between space-x-4 bg-background p-6 pb-2">
<h1 className="text-2xl font-bold">Контакты</h1>
<ContactsFilter />
</div>
<div className="bg-background p-6 pt-0">
<ContactsList />
</div>
</ContactsFilterProvider>
);
}

View File

@ -3,7 +3,7 @@ import { type PropsWithChildren } from 'react';
export default async function Layout({ children }: Readonly<PropsWithChildren>) {
return (
<div className="min-h-screen">
<div className="mx-auto flex h-screen flex-col justify-between">
<main>{children}</main>
<BottomNav />
</div>

View File

@ -1,3 +0,0 @@
export default function MastersPage() {
return 'Masters';
}

View File

@ -0,0 +1,27 @@
import { getProfile } from '@/actions/profile';
import { ProfileCard } from '@/components/profile/profile-card';
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
type Props = {
params: Promise<{
telegramId: string;
}>;
};
export default async function ProfilePage(props: Readonly<Props>) {
const parameters = await props.params;
const { telegramId } = parameters;
const queryClient = new QueryClient();
await queryClient.prefetchQuery({
queryFn: () => getProfile({ telegramId }),
queryKey: telegramId ? ['profile', 'telegramId', telegramId] : ['profile'],
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProfileCard telegramId={telegramId} />
</HydrationBoundary>
);
}

View File

@ -1,47 +1,18 @@
import { updateProfile, updateRole } from '@/actions/profile';
import { CheckboxWithText } from '@/components/profile/checkbox-with-text';
import { ProfileField } from '@/components/profile/profile-field';
import { authOptions } from '@/config/auth';
import { getCustomer } from '@repo/graphql/api';
import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar';
import { Card, CardContent, CardHeader } from '@repo/ui/components/ui/card';
import { getServerSession } from 'next-auth/next';
import { getProfile } from '@/actions/profile';
import { ProfileCard } from '@/components/profile/profile-card';
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
export default async function ProfilePage() {
const session = await getServerSession(authOptions);
const { data } = await getCustomer({ telegramId: session?.user?.telegramId });
const user = data.customers.at(0);
const photoUrl = user?.photoUrl ?? 'https://github.com/shadcn.png';
const queryClient = new QueryClient();
if (!user) return 'Профиль не найден';
await queryClient.prefetchQuery({
queryFn: () => getProfile(),
queryKey: ['profile'],
});
return (
<div className="mx-auto h-screen max-w-md">
<Card>
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
<Avatar className="size-12">
<AvatarImage alt={user?.name} src={photoUrl} />
<AvatarFallback>{user?.name.charAt(0)}</AvatarFallback>
</Avatar>
<h2 className="text-2xl font-bold">{user?.name}</h2>
</CardHeader>
<CardContent className="space-y-4">
<ProfileField
fieldName="name"
id="name"
label="Имя"
onChange={updateProfile}
value={user?.name ?? ''}
/>
<ProfileField disabled id="phone" label="Телефон" value={user?.phone ?? ''} />
<CheckboxWithText
checked={user.role !== 'client'}
description="Разрешить другим пользователям записываться к вам"
onChange={updateRole}
text="Быть мастером"
/>
</CardContent>
</Card>
</div>
<HydrationBoundary state={dehydrate(queryClient)}>
<ProfileCard />
</HydrationBoundary>
);
}

View File

@ -1,4 +1,5 @@
import { AuthProvider } from '@/providers';
import { AuthProvider } from '@/providers/auth';
import { QueryProvider } from '@/providers/query';
import { ThemeProvider } from '@/providers/theme-provider';
import { I18nProvider } from '@/utils/i18n/provider';
import '@repo/ui/globals.css';
@ -18,7 +19,9 @@ export default async function RootLayout({ children }: Readonly<PropsWithChildre
<body className="bg-secondary">
<I18nProvider>
<ThemeProvider>
<AuthProvider>{children}</AuthProvider>
<AuthProvider>
<QueryProvider>{children}</QueryProvider>
</AuthProvider>
</ThemeProvider>
</I18nProvider>
</body>

View File

@ -0,0 +1,9 @@
import { Loader2 } from 'lucide-react';
export function LoadingSpinner() {
return (
<div className="flex h-full items-center justify-center">
<Loader2 className="size-8 animate-spin text-primary" />
</div>
);
}

View File

@ -0,0 +1,46 @@
'use client';
import { LoadingSpinner } from '../common/spinner';
import { useCustomerContacts } from '@/hooks/contacts';
import * as GQL from '@repo/graphql/types';
import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar';
import Link from 'next/link';
import { memo } from 'react';
type ContactRowProps = {
readonly contact: GQL.CustomerFieldsFragment;
};
const ContactRow = memo(function ({ contact }: ContactRowProps) {
return (
<Link href={`/profile/${contact.telegramId}`} key={contact.telegramId}>
<div className="flex items-center space-x-4 rounded-lg py-2 transition-colors hover:bg-accent">
<Avatar>
<AvatarImage alt={contact.name} src={contact.photoUrl || ''} />
<AvatarFallback>{contact.name.charAt(0)}</AvatarFallback>
</Avatar>
<div>
<p className="font-medium">{contact.name}</p>
<p className="text-sm text-muted-foreground">
{contact.role === GQL.Enum_Customer_Role.Client ? 'Клиент' : 'Мастер'}
</p>
</div>
</div>
</Link>
);
});
export function ContactsList() {
const { contacts, isLoading } = useCustomerContacts();
if (isLoading) return <LoadingSpinner />;
if (!contacts.length) return <div>Контакты не найдены</div>;
return (
<div className="space-y-2">
{contacts.map((contact) => (
<ContactRow contact={contact} key={contact.documentId} />
))}
</div>
);
}

View File

@ -0,0 +1,37 @@
'use client';
import { ContactsFilterContext, type FilterType } from '@/context/contacts-filter';
import { Button } from '@repo/ui/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@repo/ui/components/ui/dropdown-menu';
import { ChevronDown } from 'lucide-react';
import { use } from 'react';
const filterLabels: Record<FilterType, string> = {
all: 'Все',
clients: 'Клиенты',
masters: 'Мастера',
};
export function ContactsFilter() {
const { filter, setFilter } = use(ContactsFilterContext);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="flex items-center space-x-1" variant="ghost">
<span>{filterLabels[filter]}</span>
<ChevronDown className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setFilter('all')}>Все</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFilter('clients')}>Клиенты</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFilter('masters')}>Мастера</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -3,12 +3,12 @@ import { BookOpen, Newspaper, PlusCircle, User, Users } from 'lucide-react';
export function BottomNav() {
return (
<nav className="fixed inset-x-0 bottom-0 border-t border-border bg-background">
<nav className="sticky inset-x-0 bottom-0 border-t border-border bg-background">
<div className="grid grid-cols-5">
<NavButton href="/dashboard" icon={<Newspaper />} label="Главное" />
<NavButton href="/records" icon={<BookOpen />} label="Записи" />
<NavButton href="/records/add" icon={<PlusCircle />} label="Новая запись" />
<NavButton href="/masters" icon={<Users />} label="Контакты" />
<NavButton href="/contacts" icon={<Users />} label="Контакты" />
<NavButton href="/profile" icon={<User />} label="Профиль" />
</div>
</nav>

View File

@ -1,14 +1,12 @@
/* eslint-disable canonical/id-match */
/* eslint-disable promise/prefer-await-to-then */
'use client';
import { Enum_Customer_Role } from '@repo/graphql/types';
import { Checkbox, type CheckboxProps } from '@repo/ui/components/ui/checkbox';
import { useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';
type Props = Pick<CheckboxProps, 'checked'> & {
readonly description?: string;
readonly onChange?: (value: Enum_Customer_Role) => Promise<void> | void;
readonly onChange?: (value: boolean) => Promise<void> | void;
readonly text: string;
};
@ -39,7 +37,7 @@ export function CheckboxWithText({ checked: initialValue, description, onChange,
}
function useDebouncedOnChangeCallback(
callback: ((value: Enum_Customer_Role) => Promise<void> | void) | undefined,
callback: ((value: boolean) => Promise<void> | void) | undefined,
) {
const [isPending, setIsPending] = useState(false);
@ -47,7 +45,7 @@ function useDebouncedOnChangeCallback(
if (!callback) return;
setIsPending(true);
const result = callback(checked ? Enum_Customer_Role.Master : Enum_Customer_Role.Client);
const result = callback(checked);
if (result instanceof Promise) {
result.finally(() => setIsPending(false));

View File

@ -0,0 +1,9 @@
/* eslint-disable canonical/id-match */
import { updateProfile } from '@/actions/profile';
import { Enum_Customer_Role } from '@repo/graphql/types';
export function updateRole(checked: boolean) {
return updateProfile({
role: checked ? Enum_Customer_Role.Master : Enum_Customer_Role.Client,
});
}

View File

@ -0,0 +1,80 @@
'use client';
import { updateRole } from './lib/actions';
import { getProfile, updateProfile } from '@/actions/profile';
import { CheckboxWithText } from '@/components/profile/checkbox-with-text';
import { ProfileField } from '@/components/profile/profile-field';
import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar';
import { Button } from '@repo/ui/components/ui/button';
import { Card, CardContent, CardHeader } from '@repo/ui/components/ui/card';
import { useQuery } from '@tanstack/react-query';
import Link from 'next/link';
type ProfileCardProps = {
readonly telegramId?: string;
};
export function ProfileCard({ telegramId }: ProfileCardProps) {
const { data: customer } = useQuery({
queryFn: () => getProfile({ telegramId }),
queryKey: telegramId ? ['profile', 'telegramId', telegramId] : ['profile'],
});
if (!customer) return <div>Пользователь не найден</div>;
return (
<Card>
<CardHeader className="flex flex-row items-center space-x-4 pb-2">
<Avatar className="size-12">
<AvatarImage alt={customer?.name} src={customer.photoUrl || ''} />
<AvatarFallback>{customer?.name.charAt(0)}</AvatarFallback>
</Avatar>
<h2 className="text-2xl font-bold">{customer?.name}</h2>
</CardHeader>
<CardContent className="space-y-4">
{telegramId ? (
false
) : (
<ProfileField
fieldName="name"
id="name"
label="Имя"
onChange={updateProfile}
value={customer?.name ?? ''}
/>
)}
<Link href={telegramId && customer?.phone ? `tel:${customer?.phone}` : ''}>
<ProfileField
disabled={!telegramId}
id="phone"
label="Телефон"
readOnly={Boolean(telegramId)}
value={customer?.phone ?? ''}
/>
</Link>
{telegramId ? (
false
) : (
<CheckboxWithText
checked={customer.role !== 'client'}
description="Разрешить другим пользователям записываться к вам"
onChange={updateRole}
text="Быть мастером"
/>
)}
{telegramId ? (
<Button asChild className="w-full bg-foreground">
<Link
href={`https://t.me/${customer?.phone}`}
rel="noopener noreferrer"
target="_blank"
>
Написать в Telegram
</Link>
</Button>
) : (
false
)}
</CardContent>
</Card>
);
}

View File

@ -12,6 +12,7 @@ type ProfileFieldProps = {
readonly id: string;
readonly label: string;
readonly onChange?: (value: CustomerInput) => Promise<void> | void;
readonly readOnly?: boolean;
readonly value: string;
};
@ -21,6 +22,7 @@ export function ProfileField({
id,
label,
onChange,
readOnly,
value: initialValue,
}: ProfileFieldProps) {
const [value, setValue] = useState(initialValue);
@ -41,6 +43,7 @@ export function ProfileField({
disabled={disabled || isPending}
id={id}
onChange={handleChange}
readOnly={readOnly}
ref={inputRef}
value={value}
/>
@ -75,11 +78,18 @@ function useDebouncedOnChangeCallback(
function useFocus(isPending: boolean) {
const inputRef = useRef<HTMLInputElement | null>(null);
const [isInitialRender, setIsInitialRender] = useState(true);
useEffect(() => {
if (inputRef.current) {
if (isInitialRender) {
setIsInitialRender(false);
return;
}
if (inputRef.current && isPending) {
inputRef.current.focus();
}
}, [isPending]);
}, [isInitialRender, isPending]);
return inputRef;
}

View File

@ -0,0 +1,15 @@
'use client';
import { createContext, useMemo, useState } from 'react';
export type FilterType = 'all' | 'clients' | 'masters';
type ContextType = { filter: FilterType; setFilter: (filter: FilterType) => void };
export const ContactsFilterContext = createContext<ContextType>({} as ContextType);
export function ContactsFilterProvider({ children }: { readonly children: React.ReactNode }) {
const [filter, setFilter] = useState<FilterType>('all');
const value = useMemo(() => ({ filter, setFilter }), [filter, setFilter]);
return <ContactsFilterContext value={value}>{children}</ContactsFilterContext>;
}

View File

@ -0,0 +1 @@
export * from './use-customer-contacts';

View File

@ -0,0 +1,37 @@
'use client';
import { getClients, getMasters } from '@/actions/contacts';
import { ContactsFilterContext } from '@/context/contacts-filter';
import { useQuery } from '@tanstack/react-query';
import { sift } from 'radash';
import { use, useMemo } from 'react';
export function useCustomerContacts() {
const { filter } = use(ContactsFilterContext);
const { data: clientsData, isLoading: isLoadingClients } = useQuery({
queryFn: getClients,
queryKey: ['contacts', 'clients'],
});
const clients = clientsData?.clients;
const { data: mastersData, isLoading: isLoadingMasters } = useQuery({
queryFn: getMasters,
queryKey: ['contacts', 'masters'],
});
const masters = mastersData?.masters;
const contacts = useMemo(() => {
switch (filter) {
case 'clients':
return clients ? sift(clients) : [];
case 'masters':
return masters ? sift(masters) : [];
default:
return [...(clients ? sift(clients) : []), ...(masters ? sift(masters) : [])];
}
}, [clients, masters, filter]);
return { contacts, isLoading: isLoadingClients || isLoadingMasters };
}

View File

@ -15,6 +15,7 @@
},
"dependencies": {
"@repo/ui": "workspace:*",
"@tanstack/react-query": "^5.64.1",
"@telegram-apps/sdk-react": "^2.0.19",
"graphql": "catalog:",
"lucide-react": "catalog:",
@ -22,6 +23,7 @@
"next-auth": "^4.24.11",
"next-intl": "^3.26.0",
"next-themes": "^0.4.4",
"radash": "^12.1.0",
"react": "catalog:",
"react-dom": "catalog:",
"use-debounce": "^10.0.4",

View File

@ -1,2 +0,0 @@
export * from './auth';
export * from './telegram';

View File

@ -0,0 +1,42 @@
'use client';
// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query';
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1_000,
},
},
});
}
let browserQueryClient: QueryClient | undefined;
export function QueryProvider({ children }: { readonly children: React.ReactNode }) {
// NOTE: Avoid useState when initializing the query client if you don't
// have a suspense boundary between this and the code that may
// suspend because React will throw away the client on the initial
// render if it suspends and there is no boundary
const queryClient = getQueryClient();
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}
function getQueryClient() {
if (isServer) {
// Server: always make a new query client
return makeQueryClient();
} else {
// Browser: make a new query client if we don't already have one
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
}

View File

@ -2,29 +2,113 @@
import { getClientWithToken } from '../apollo/client';
import * as GQL from '../types';
export async function createCustomer(variables: GQL.CreateCustomerMutationVariables) {
const { mutate } = await getClientWithToken();
export async function createOrUpdateUser(input: GQL.CreateCustomerMutationVariables) {
if (!input.phone && !input.telegramId) throw new Error('Missing phone and telegramId');
const { query, mutate } = await getClientWithToken();
const response = await query({
query: GQL.GetCustomerDocument,
variables: input,
});
const customer = response?.data?.customers?.at(0);
if (customer && customer.phone === input.phone) {
return mutate({
mutation: GQL.UpdateCustomerProfileDocument,
variables: {
documentId: customer.documentId,
data: { ...input },
},
});
}
return mutate({
mutation: GQL.CreateCustomerDocument,
variables,
variables: input,
});
}
export async function getCustomer(variables: GQL.GetCustomerQueryVariables) {
export async function getCustomer(input: GQL.GetCustomerQueryVariables) {
const { query } = await getClientWithToken();
return query({
const response = await query({
query: GQL.GetCustomerDocument,
variables,
variables: input,
});
return response?.data?.customers?.at(0);
}
export async function updateCustomerProfile(variables: GQL.UpdateCustomerProfileMutationVariables) {
export async function getCustomerMasters(input: GQL.GetCustomerMastersQueryVariables) {
const { query } = await getClientWithToken();
const response = await query({
query: GQL.GetCustomerMastersDocument,
variables: input,
});
return response?.data?.customers?.at(0);
}
export async function getCustomerClients(input: GQL.GetCustomerClientsQueryVariables) {
const { query } = await getClientWithToken();
const response = await query({
query: GQL.GetCustomerClientsDocument,
variables: input,
});
return response?.data?.customers?.at(0);
}
type AddCustomerMasterInput = Pick<GQL.CreateCustomerMutationVariables, 'phone' | 'telegramId'> & {
masterId: GQL.Scalars['ID']['input'];
operation: 'add' | 'remove';
};
export async function updateCustomerMaster(input: AddCustomerMasterInput) {
if (!input.phone && !input.telegramId) throw new Error('Missing phone and telegramId');
const { query } = await getClientWithToken();
const response = await query({
query: GQL.GetCustomerMastersDocument,
variables: input,
});
const customer = response?.data?.customers?.at(0);
if (customer) {
let newMastersIds = customer.masters.map((x) => x?.documentId);
switch (input.operation) {
case 'add':
if (newMastersIds.includes(input.masterId)) return;
newMastersIds = [...newMastersIds, input.masterId];
break;
case 'remove':
newMastersIds = newMastersIds.filter((x) => x !== input.masterId);
break;
default:
break;
}
return updateCustomerProfile({
documentId: customer.documentId,
data: {
masters: newMastersIds,
},
});
}
}
export async function updateCustomerProfile(input: GQL.UpdateCustomerProfileMutationVariables) {
const { mutate } = await getClientWithToken();
return mutate({
mutation: GQL.UpdateCustomerProfileDocument,
variables,
variables: input,
});
}

View File

@ -1,4 +1,4 @@
fragment CustomerProfile on Customer {
fragment CustomerFields on Customer {
active
documentId
name
@ -8,20 +8,48 @@ fragment CustomerProfile on Customer {
telegramId
}
mutation CreateCustomer($name: String!, $telegramId: Long!, $phone: String!) {
mutation CreateCustomer($name: String!, $telegramId: Long, $phone: String) {
createCustomer(data: { name: $name, telegramId: $telegramId, phone: $phone, role: client }) {
...CustomerProfile
documentId
}
}
query GetCustomer($telegramId: Long!) {
customers(filters: { telegramId: { eq: $telegramId } }) {
...CustomerProfile
query GetCustomer($phone: String, $telegramId: Long) {
customers(filters: { or: [{ phone: { eq: $phone } }, { telegramId: { eq: $telegramId } }] }) {
...CustomerFields
}
}
query GetCustomerMasters($phone: String, $telegramId: Long) {
customers(
filters: {
or: [{ phone: { eq: $phone } }, { telegramId: { eq: $telegramId } }]
and: [{ active: { eq: true } }]
}
) {
documentId
masters {
...CustomerFields
}
}
}
query GetCustomerClients($phone: String, $telegramId: Long) {
customers(
filters: {
or: [{ phone: { eq: $phone } }, { telegramId: { eq: $telegramId } }]
and: [{ active: { eq: true } }]
}
) {
documentId
clients {
...CustomerFields
}
}
}
mutation UpdateCustomerProfile($documentId: ID!, $data: CustomerInput!) {
updateCustomer(documentId: $documentId, data: $data) {
...CustomerProfile
...CustomerFields
}
}

View File

@ -567,24 +567,41 @@ export type LoginMutationVariables = Exact<{
export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | null | undefined } };
export type CustomerProfileFragment = { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined };
export type CustomerFieldsFragment = { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined };
export type CreateCustomerMutationVariables = Exact<{
name: Scalars['String']['input'];
telegramId: Scalars['Long']['input'];
phone: Scalars['String']['input'];
telegramId?: InputMaybe<Scalars['Long']['input']>;
phone?: InputMaybe<Scalars['String']['input']>;
}>;
export type CreateCustomerMutation = { __typename?: 'Mutation', createCustomer?: { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined } | null | undefined };
export type CreateCustomerMutation = { __typename?: 'Mutation', createCustomer?: { __typename?: 'Customer', documentId: string } | null | undefined };
export type GetCustomerQueryVariables = Exact<{
telegramId: Scalars['Long']['input'];
phone?: InputMaybe<Scalars['String']['input']>;
telegramId?: InputMaybe<Scalars['Long']['input']>;
}>;
export type GetCustomerQuery = { __typename?: 'Query', customers: Array<{ __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined } | null | undefined> };
export type GetCustomerMastersQueryVariables = Exact<{
phone?: InputMaybe<Scalars['String']['input']>;
telegramId?: InputMaybe<Scalars['Long']['input']>;
}>;
export type GetCustomerMastersQuery = { __typename?: 'Query', customers: Array<{ __typename?: 'Customer', documentId: string, masters: Array<{ __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined } | null | undefined> } | null | undefined> };
export type GetCustomerClientsQueryVariables = Exact<{
phone?: InputMaybe<Scalars['String']['input']>;
telegramId?: InputMaybe<Scalars['Long']['input']>;
}>;
export type GetCustomerClientsQuery = { __typename?: 'Query', customers: Array<{ __typename?: 'Customer', documentId: string, clients: Array<{ __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined } | null | undefined> } | null | undefined> };
export type UpdateCustomerProfileMutationVariables = Exact<{
documentId: Scalars['ID']['input'];
data: CustomerInput;
@ -593,9 +610,11 @@ export type UpdateCustomerProfileMutationVariables = Exact<{
export type UpdateCustomerProfileMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: any | null | undefined } | null | undefined };
export const CustomerProfileFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerProfile"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<CustomerProfileFragment, unknown>;
export const CustomerFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<CustomerFieldsFragment, unknown>;
export const RegisterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Register"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"identifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"register"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"identifier"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"jwt"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"username"}}]}}]}}]}}]} as unknown as DocumentNode<RegisterMutation, RegisterMutationVariables>;
export const LoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Login"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"identifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"login"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"identifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"identifier"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"jwt"}}]}}]}}]} as unknown as DocumentNode<LoginMutation, LoginMutationVariables>;
export const CreateCustomerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateCustomer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"phone"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createCustomer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"phone"},"value":{"kind":"Variable","name":{"kind":"Name","value":"phone"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"role"},"value":{"kind":"EnumValue","value":"client"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerProfile"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerProfile"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<CreateCustomerMutation, CreateCustomerMutationVariables>;
export const GetCustomerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCustomer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerProfile"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerProfile"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<GetCustomerQuery, GetCustomerQueryVariables>;
export const UpdateCustomerProfileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCustomerProfile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CustomerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCustomer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerProfile"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerProfile"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<UpdateCustomerProfileMutation, UpdateCustomerProfileMutationVariables>;
export const CreateCustomerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateCustomer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"name"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"phone"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createCustomer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"name"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"phone"},"value":{"kind":"Variable","name":{"kind":"Name","value":"phone"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"role"},"value":{"kind":"EnumValue","value":"client"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}}]}}]}}]} as unknown as DocumentNode<CreateCustomerMutation, CreateCustomerMutationVariables>;
export const GetCustomerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCustomer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"phone"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"phone"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"phone"}}}]}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<GetCustomerQuery, GetCustomerQueryVariables>;
export const GetCustomerMastersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCustomerMasters"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"phone"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"phone"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"phone"}}}]}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}}]}}]}]}},{"kind":"ObjectField","name":{"kind":"Name","value":"and"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"BooleanValue","value":true}}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"masters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<GetCustomerMastersQuery, GetCustomerMastersQueryVariables>;
export const GetCustomerClientsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetCustomerClients"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"phone"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Long"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"customers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"or"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"phone"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"phone"}}}]}}]},{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"telegramId"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"telegramId"}}}]}}]}]}},{"kind":"ObjectField","name":{"kind":"Name","value":"and"},"value":{"kind":"ListValue","values":[{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"active"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"BooleanValue","value":true}}]}}]}]}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"clients"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerFields"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<GetCustomerClientsQuery, GetCustomerClientsQueryVariables>;
export const UpdateCustomerProfileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCustomerProfile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CustomerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCustomer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CustomerFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CustomerFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Customer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"active"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"phone"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"telegramId"}}]}}]} as unknown as DocumentNode<UpdateCustomerProfileMutation, UpdateCustomerProfileMutationVariables>;

View File

@ -46,7 +46,10 @@
"dependencies": {
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"react": "catalog:",
"react-dom": "catalog:"
}

View File

@ -0,0 +1,187 @@
'use client';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { cn } from '@repo/ui/lib/utils';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
readonly inset?: boolean;
}
>(({ children, className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
className={cn(
'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className,
)}
ref={ref}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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',
className,
)}
ref={ref}
{...props}
/>
));
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
className={cn(
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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',
className,
)}
ref={ref}
sideOffset={sideOffset}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
readonly inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
className={cn(
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
className,
)}
ref={ref}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ checked, children, className, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
checked={checked}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
ref={ref}
{...props}
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="size-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ children, className, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
className={cn(
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
ref={ref}
{...props}
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
readonly inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
ref={ref}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
className={cn('-mx-1 my-1 h-px bg-muted', className)}
ref={ref}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
function DropdownMenuShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
return (
<span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
);
}
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
};

View File

@ -0,0 +1,45 @@
'use client';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from '@repo/ui/lib/utils';
import * as React from 'react';
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ children, className, ...props }, ref) => (
<ScrollAreaPrimitive.Root
className={cn('relative overflow-hidden', className)}
ref={ref}
{...props}
>
<ScrollAreaPrimitive.Viewport className="size-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = 'vertical', ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
className={cn(
'flex touch-none select-none transition-colors',
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
className,
)}
orientation={orientation}
ref={ref}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar };

609
pnpm-lock.yaml generated
View File

@ -141,6 +141,9 @@ importers:
'@repo/ui':
specifier: workspace:*
version: link:../../packages/ui
'@tanstack/react-query':
specifier: ^5.64.1
version: 5.64.1(react@19.0.0)
'@telegram-apps/sdk-react':
specifier: ^2.0.19
version: 2.0.19(@types/react@19.0.1)(react@19.0.0)
@ -162,6 +165,9 @@ importers:
next-themes:
specifier: ^0.4.4
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
radash:
specifier: ^12.1.0
version: 12.1.0
react:
specifier: 'catalog:'
version: 19.0.0
@ -306,9 +312,18 @@ importers:
'@radix-ui/react-checkbox':
specifier: ^1.1.3
version: 1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-dropdown-menu':
specifier: ^2.1.4
version: 2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-label':
specifier: ^2.1.1
version: 2.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-scroll-area':
specifier: ^1.2.2
version: 1.2.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-select':
specifier: ^2.1.4
version: 2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react:
specifier: 'catalog:'
version: 19.0.0
@ -1341,6 +1356,21 @@ packages:
resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@floating-ui/core@1.6.9':
resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
'@floating-ui/dom@1.6.13':
resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
'@floating-ui/react-dom@2.1.2':
resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
'@formatjs/ecma402-abstract@2.3.1':
resolution: {integrity: sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==}
@ -1950,9 +1980,25 @@ packages:
engines: {node: '>=18'}
hasBin: true
'@radix-ui/number@1.1.0':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
'@radix-ui/primitive@1.1.1':
resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
'@radix-ui/react-arrow@1.1.1':
resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-avatar@1.1.2':
resolution: {integrity: sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig==}
peerDependencies:
@ -1979,6 +2025,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.1.1':
resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.1':
resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
peerDependencies:
@ -1997,6 +2056,72 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-direction@1.1.0':
resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dismissable-layer@1.1.3':
resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-dropdown-menu@2.1.4':
resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-focus-guards@1.1.1':
resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-focus-scope@1.1.1':
resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-id@1.1.0':
resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-label@2.1.1':
resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==}
peerDependencies:
@ -2010,6 +2135,45 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-menu@2.1.4':
resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-popper@1.2.1':
resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-portal@1.1.3':
resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-presence@1.1.2':
resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
peerDependencies:
@ -2036,6 +2200,45 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.1':
resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-scroll-area@1.2.2':
resolution: {integrity: sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.1.4':
resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.1.1':
resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==}
peerDependencies:
@ -2063,6 +2266,15 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-escape-keydown@1.1.0':
resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.0':
resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
peerDependencies:
@ -2081,6 +2293,15 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-rect@1.1.0':
resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-size@1.1.0':
resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
peerDependencies:
@ -2090,6 +2311,22 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-visually-hidden@1.1.1':
resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
'@repeaterjs/repeater@3.0.4':
resolution: {integrity: sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==}
@ -2203,6 +2440,14 @@ packages:
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@tanstack/query-core@5.64.1':
resolution: {integrity: sha512-978Wx4Wl4UJZbmvU/rkaM9cQtXXrbhK0lsz/UZhYIbyKYA8E4LdomTwyh2GHZ4oU0BKKoDH4YlKk2VscCUgNmg==}
'@tanstack/react-query@5.64.1':
resolution: {integrity: sha512-vW5ggHpIO2Yjj44b4sB+Fd3cdnlMJppXRBJkEHvld6FXh3j5dwWJoQo7mGtKI2RbSFyiyu/PhGAy0+Vv5ev9Eg==}
peerDependencies:
react: ^18 || ^19
'@telegraf/types@7.1.0':
resolution: {integrity: sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==}
@ -2585,6 +2830,10 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
aria-hidden@1.2.4:
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
engines: {node: '>=10'}
aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
@ -3075,6 +3324,9 @@ packages:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
detect-node-es@1.1.0:
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
@ -3700,6 +3952,10 @@ packages:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
get-nonce@1.0.1:
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
engines: {node: '>=6'}
get-set-props@0.1.0:
resolution: {integrity: sha512-7oKuKzAGKj0ag+eWZwcGw2fjiZ78tXnXQoBgY0aU7ZOxTu4bB7hSuQSDgtKy978EDH062P5FmD2EWiDpQS9K9Q==}
engines: {node: '>=0.10.0'}
@ -4961,6 +5217,10 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
radash@12.1.0:
resolution: {integrity: sha512-b0Zcf09AhqKS83btmUeYBS8tFK7XL2e3RvLmZcm0sTdF1/UUlHSsjXdCcWNxe7yfmAlPve5ym0DmKGtTzP6kVQ==}
engines: {node: '>=14.18.0'}
rambda@7.5.0:
resolution: {integrity: sha512-y/M9weqWAH4iopRd7EHDEQQvpFPHj1AA3oHozE9tfITHUtTR7Z9PSlIRRG2l1GuW7sefC1cXFfIcF+cgnShdBA==}
@ -4982,6 +5242,36 @@ packages:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
react-remove-scroll@2.6.2:
resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
@ -5728,6 +6018,16 @@ packages:
urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
use-callback-ref@1.3.3:
resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
use-debounce@10.0.4:
resolution: {integrity: sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==}
engines: {node: '>= 16.0.0'}
@ -5739,6 +6039,16 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@ -7061,6 +7371,23 @@ snapshots:
dependencies:
levn: 0.4.1
'@floating-ui/core@1.6.9':
dependencies:
'@floating-ui/utils': 0.2.9
'@floating-ui/dom@1.6.13':
dependencies:
'@floating-ui/core': 1.6.9
'@floating-ui/utils': 0.2.9
'@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@floating-ui/dom': 1.6.13
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
'@floating-ui/utils@0.2.9': {}
'@formatjs/ecma402-abstract@2.3.1':
dependencies:
'@formatjs/fast-memoize': 2.2.5
@ -7961,8 +8288,19 @@ snapshots:
dependencies:
playwright: 1.49.1
'@radix-ui/number@1.1.0': {}
'@radix-ui/primitive@1.1.1': {}
'@radix-ui/react-arrow@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-avatar@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
@ -7991,6 +8329,18 @@ snapshots:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-collection@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-slot': 1.1.1(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-compose-refs@1.1.1(@types/react@19.0.1)(react@19.0.0)':
dependencies:
react: 19.0.0
@ -8003,6 +8353,64 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-direction@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-menu': 2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.1)(react@19.0.0)':
dependencies:
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-focus-scope@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-id@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-label@2.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -8012,6 +8420,60 @@ snapshots:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-menu@2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-slot': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
aria-hidden: 1.2.4
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
react-remove-scroll: 2.6.2(@types/react@19.0.1)(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-popper@1.2.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-arrow': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-size': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/rect': 1.1.0
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-portal@1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-presence@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
@ -8031,6 +8493,69 @@ snapshots:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-roving-focus@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-scroll-area@1.2.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-select@2.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/number': 1.1.0
'@radix-ui/primitive': 1.1.1
'@radix-ui/react-collection': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-popper': 1.2.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-portal': 1.1.3(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-slot': 1.1.1(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-use-previous': 1.1.0(@types/react@19.0.1)(react@19.0.0)
'@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
aria-hidden: 1.2.4
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
react-remove-scroll: 2.6.2(@types/react@19.0.1)(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/react-slot@1.1.1(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.1)(react@19.0.0)
@ -8051,6 +8576,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
react: 19.0.0
@ -8063,6 +8595,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-use-rect@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@radix-ui/rect': 1.1.0
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-use-size@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
@ -8070,6 +8609,17 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.1
'@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
'@radix-ui/rect@1.1.0': {}
'@repeaterjs/repeater@3.0.4': {}
'@repeaterjs/repeater@3.0.6': {}
@ -8149,6 +8699,13 @@ snapshots:
dependencies:
tslib: 2.8.1
'@tanstack/query-core@5.64.1': {}
'@tanstack/react-query@5.64.1(react@19.0.0)':
dependencies:
'@tanstack/query-core': 5.64.1
react: 19.0.0
'@telegraf/types@7.1.0': {}
'@telegram-apps/bridge@1.7.1':
@ -8628,6 +9185,10 @@ snapshots:
argparse@2.0.1: {}
aria-hidden@1.2.4:
dependencies:
tslib: 2.8.1
aria-query@5.3.0:
dependencies:
dequal: 2.0.3
@ -9170,6 +9731,8 @@ snapshots:
detect-libc@2.0.3:
optional: true
detect-node-es@1.1.0: {}
didyoumean@1.2.2: {}
dir-glob@3.0.1:
@ -10087,6 +10650,8 @@ snapshots:
has-symbols: 1.0.3
hasown: 2.0.2
get-nonce@1.0.1: {}
get-set-props@0.1.0: {}
get-stream@8.0.1: {}
@ -11347,6 +11912,8 @@ snapshots:
queue-microtask@1.2.3: {}
radash@12.1.0: {}
rambda@7.5.0: {}
ramda@0.30.1: {}
@ -11362,6 +11929,33 @@ snapshots:
react-refresh@0.14.2: {}
react-remove-scroll-bar@2.3.8(@types/react@19.0.1)(react@19.0.0):
dependencies:
react: 19.0.0
react-style-singleton: 2.2.3(@types/react@19.0.1)(react@19.0.0)
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.0.1
react-remove-scroll@2.6.2(@types/react@19.0.1)(react@19.0.0):
dependencies:
react: 19.0.0
react-remove-scroll-bar: 2.3.8(@types/react@19.0.1)(react@19.0.0)
react-style-singleton: 2.2.3(@types/react@19.0.1)(react@19.0.0)
tslib: 2.8.1
use-callback-ref: 1.3.3(@types/react@19.0.1)(react@19.0.0)
use-sidecar: 1.1.3(@types/react@19.0.1)(react@19.0.0)
optionalDependencies:
'@types/react': 19.0.1
react-style-singleton@2.2.3(@types/react@19.0.1)(react@19.0.0):
dependencies:
get-nonce: 1.0.1
react: 19.0.0
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.0.1
react@19.0.0: {}
read-cache@1.0.0:
@ -12182,6 +12776,13 @@ snapshots:
urlpattern-polyfill@8.0.2: {}
use-callback-ref@1.3.3(@types/react@19.0.1)(react@19.0.0):
dependencies:
react: 19.0.0
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.0.1
use-debounce@10.0.4(react@19.0.0):
dependencies:
react: 19.0.0
@ -12192,6 +12793,14 @@ snapshots:
intl-messageformat: 10.7.10
react: 19.0.0
use-sidecar@1.1.3(@types/react@19.0.1)(react@19.0.0):
dependencies:
detect-node-es: 1.1.0
react: 19.0.0
tslib: 2.8.1
optionalDependencies:
'@types/react': 19.0.1
util-deprecate@1.0.2: {}
uuid@8.3.2: {}