diff --git a/apps/bot/src/index.ts b/apps/bot/src/index.ts index 2e9c648..01c3d3e 100644 --- a/apps/bot/src/index.ts +++ b/apps/bot/src/index.ts @@ -3,12 +3,7 @@ import { env as environment } from './config/env'; import { commandsList, KEYBOARD_REMOVE, KEYBOARD_SHARE_PHONE, MESSAGE_NOT_MASTER } from './message'; import { normalizePhoneNumber } from './utils/phone'; -import { - createOrUpdateUser, - getCustomer, - updateCustomerMaster, - updateCustomerProfile, -} from '@repo/graphql/api'; +import { CustomersService } from '@repo/graphql/api/customers'; import { Enum_Customer_Role } from '@repo/graphql/types'; import { Telegraf } from 'telegraf'; import { message } from 'telegraf/filters'; @@ -16,8 +11,10 @@ import { message } from 'telegraf/filters'; const bot = new Telegraf(environment.BOT_TOKEN); bot.start(async (context) => { - const data = await getCustomer({ telegramId: context.from.id }); - const customer = data?.data?.customers?.at(0); + const telegramId = context.from.id; + + const customerService = new CustomersService({ telegramId }); + const { customer } = await customerService.getCustomer({ telegramId }); if (customer) { return context.reply( @@ -34,8 +31,10 @@ bot.start(async (context) => { }); bot.command('addcontact', async (context) => { - const data = await getCustomer({ telegramId: context.from.id }); - const customer = data?.data?.customers?.at(0); + const telegramId = context.from.id; + + const customerService = new CustomersService({ telegramId }); + const { customer } = await customerService.getCustomer({ telegramId }); if (!customer) { return context.reply( @@ -52,8 +51,10 @@ bot.command('addcontact', async (context) => { }); bot.command('becomemaster', async (context) => { - const data = await getCustomer({ telegramId: context.from.id }); - const customer = data?.data?.customers?.at(0); + const telegramId = context.from.id; + + const customerService = new CustomersService({ telegramId }); + const { customer } = await customerService.getCustomer({ telegramId }); if (!customer) { return context.reply('Сначала поделитесь своим номером телефона.', KEYBOARD_SHARE_PHONE); @@ -63,12 +64,15 @@ bot.command('becomemaster', async (context) => { return context.reply('Вы уже являетесь мастером.'); } - const response = await updateCustomerProfile({ - data: { role: Enum_Customer_Role.Master }, - documentId: customer.documentId, - }).catch((error) => { - context.reply('Произошла ошибка.\n' + error); - }); + const response = await customerService + .updateCustomer({ + data: { + role: Enum_Customer_Role.Master, + }, + }) + .catch((error) => { + context.reply('Произошла ошибка.\n' + error); + }); if (response) { return context.reply('Вы стали мастером'); @@ -76,8 +80,10 @@ bot.command('becomemaster', async (context) => { }); bot.on(message('contact'), async (context) => { - const data = await getCustomer({ telegramId: context.from.id }); - const customer = data?.data?.customers?.at(0); + const telegramId = context.from.id; + + const customerService = new CustomersService({ telegramId }); + const { customer } = await customerService.getCustomer({ telegramId }); const isRegistration = !customer; @@ -86,13 +92,15 @@ bot.on(message('contact'), async (context) => { const phone = normalizePhoneNumber(contact.phone_number); if (isRegistration) { - const response = await createOrUpdateUser({ - name, - phone, - telegramId: context.from.id, - }).catch((error) => { - context.reply('Произошла ошибка.\n' + error); - }); + const response = await customerService + .createCustomer({ + name, + phone, + telegramId: context.from.id, + }) + .catch((error) => { + context.reply('Произошла ошибка.\n' + error); + }); if (response) { return context.reply( @@ -107,12 +115,19 @@ bot.on(message('contact'), async (context) => { } try { - await createOrUpdateUser({ name, phone }); + const createCustomerResult = await customerService.createCustomer({ name, phone }); - await updateCustomerMaster({ - masterId: customer.documentId, - operation: 'add', - phone, + const documentId = createCustomerResult?.createCustomer?.documentId; + + if (!documentId) { + throw new Error('Customer not created'); + } + + const masters = [customer.documentId]; + + await customerService.addMasters({ + data: { masters }, + documentId, }); return context.reply( diff --git a/apps/web/actions/api/customers.ts b/apps/web/actions/api/customers.ts new file mode 100644 index 0000000..9ea6faf --- /dev/null +++ b/apps/web/actions/api/customers.ts @@ -0,0 +1,42 @@ +'use server'; + +import { useService } from './lib/service'; +import { CustomersService } from '@repo/graphql/api/customers'; + +const getService = useService(CustomersService); + +export async function addMasters(...variables: Parameters) { + const service = await getService(); + + return service.addMasters(...variables); +} + +export async function createCustomer(...variables: Parameters) { + const service = await getService(); + + return service.createCustomer(...variables); +} + +export async function getClients(...variables: Parameters) { + const service = await getService(); + + return service.getClients(...variables); +} + +export async function getCustomer(...variables: Parameters) { + const service = await getService(); + + return service.getCustomer(...variables); +} + +export async function getMasters(...variables: Parameters) { + const service = await getService(); + + return service.getMasters(...variables); +} + +export async function updateCustomer(...variables: Parameters) { + const service = await getService(); + + return service.updateCustomer(...variables); +} diff --git a/apps/web/actions/api/lib/service.ts b/apps/web/actions/api/lib/service.ts new file mode 100644 index 0000000..ca34466 --- /dev/null +++ b/apps/web/actions/api/lib/service.ts @@ -0,0 +1,14 @@ +import { authOptions } from '@/config/auth'; +import { type BaseService } from '@repo/graphql/api/base'; +import { getServerSession } from 'next-auth'; + +export function useService(service: T) { + return async function () { + const session = await getServerSession(authOptions); + if (!session?.user?.telegramId) throw new Error('Unauthorized'); + + const customer = { telegramId: session.user.telegramId }; + + return new service(customer) as InstanceType; + }; +} diff --git a/apps/web/actions/api/orders.ts b/apps/web/actions/api/orders.ts new file mode 100644 index 0000000..286a719 --- /dev/null +++ b/apps/web/actions/api/orders.ts @@ -0,0 +1,18 @@ +'use server'; + +import { useService } from './lib/service'; +import { OrdersService } from '@repo/graphql/api/orders'; + +const getServicesService = useService(OrdersService); + +export async function createOrder(...variables: Parameters) { + const service = await getServicesService(); + + return service.createOrder(...variables); +} + +export async function getOrder(...variables: Parameters) { + const service = await getServicesService(); + + return service.getOrder(...variables); +} diff --git a/apps/web/actions/api/services.ts b/apps/web/actions/api/services.ts new file mode 100644 index 0000000..577a04d --- /dev/null +++ b/apps/web/actions/api/services.ts @@ -0,0 +1,18 @@ +'use server'; + +import { useService } from './lib/service'; +import { ServicesService } from '@repo/graphql/api/services'; + +const getServicesService = useService(ServicesService); + +export async function getService(...variables: Parameters) { + const service = await getServicesService(); + + return service.getService(...variables); +} + +export async function getServices(...variables: Parameters) { + const service = await getServicesService(); + + return service.getServices(...variables); +} diff --git a/apps/web/actions/api/slots.ts b/apps/web/actions/api/slots.ts new file mode 100644 index 0000000..eba96cd --- /dev/null +++ b/apps/web/actions/api/slots.ts @@ -0,0 +1,36 @@ +'use server'; + +import { useService } from './lib/service'; +import { SlotsService } from '@repo/graphql/api/slots'; + +const getService = useService(SlotsService); + +export async function createSlot(...variables: Parameters) { + const service = await getService(); + + return service.createSlot(...variables); +} + +export async function deleteSlot(...variables: Parameters) { + const service = await getService(); + + return service.deleteSlot(...variables); +} + +export async function getSlot(...variables: Parameters) { + const service = await getService(); + + return service.getSlot(...variables); +} + +export async function getSlots(...variables: Parameters) { + const service = await getService(); + + return service.getSlots(...variables); +} + +export async function updateSlot(...variables: Parameters) { + const service = await getService(); + + return service.updateSlot(...variables); +} diff --git a/apps/web/actions/contacts.ts b/apps/web/actions/contacts.ts deleted file mode 100644 index 3b1bcf4..0000000 --- a/apps/web/actions/contacts.ts +++ /dev/null @@ -1,26 +0,0 @@ -'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 response = await getCustomerClients({ telegramId: user?.telegramId }); - - return response.data?.customers?.at(0); -} - -export async function getMasters() { - const session = await getServerSession(authOptions); - if (!session) throw new Error('Missing session'); - - const { user } = session; - - const response = await getCustomerMasters({ telegramId: user?.telegramId }); - - return response.data?.customers?.at(0); -} diff --git a/apps/web/actions/orders.ts b/apps/web/actions/orders.ts deleted file mode 100644 index 600ff09..0000000 --- a/apps/web/actions/orders.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable canonical/id-match */ -'use server'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '../../../packages/graphql/node_modules/@apollo/client/core'; -import { getProfile } from './profile'; -import { formatDate, formatTime, sumTime } from '@/utils/date'; -import * as api from '@repo/graphql/api'; -import { Enum_Customer_Role, Enum_Slot_State } from '@repo/graphql/types'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -export const getOrder = api.getOrder; - -type OrderInput = { - clientId: string; - date: Date; - serviceId: string; - slotId: string; - time: string; -}; - -export async function createOrder(input: OrderInput) { - const customer = await getProfile(); - - if (!input.slotId) { - throw new Error('Missing slot'); - } - - const { data } = await api.getSlot({ documentId: input.slotId }); - - if (data.slot?.state === Enum_Slot_State.Closed) { - throw new Error('Slot is closed'); - } - - if (customer.role === Enum_Customer_Role.Client) { - if (customer.documentId !== input.clientId) { - throw new Error('Invalid client'); - } - - const masterId = data.slot?.master?.documentId; - - const masters = await api.getCustomerMasters(customer); - - if (!masters.data.customers.some((master) => master?.documentId === masterId)) { - throw new Error('Invalid master'); - } - } - - if ( - customer.role === Enum_Customer_Role.Master && - data.slot?.master?.documentId !== customer.documentId - ) { - throw new Error('Invalid master'); - } - - const service = await api.getService({ documentId: input.serviceId }); - - const endTime = sumTime(input.time, service?.data?.service?.duration); - const payload = { - client: input.clientId, - date: formatDate(input.date).db(), - services: [input.serviceId], - slot: input.slotId, - time_end: formatTime(endTime).db(), - time_start: formatTime(input.time).db(), - }; - return api.createOrder(payload); -} diff --git a/apps/web/actions/profile.ts b/apps/web/actions/profile.ts deleted file mode 100644 index 021ec7a..0000000 --- a/apps/web/actions/profile.ts +++ /dev/null @@ -1,37 +0,0 @@ -'use server'; -import { authOptions } from '@/config/auth'; -import { getCustomer, updateCustomerProfile } from '@repo/graphql/api'; -import { type CustomerInput, type GetCustomerQueryVariables } from '@repo/graphql/types'; -import { getServerSession } from 'next-auth/next'; - -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 { data } = await getCustomer({ telegramId }); - const customer = data?.customers?.at(0); - - if (!customer) throw new Error('Customer not found'); - - return customer; -} - -export async function updateProfile(input: CustomerInput) { - const session = await getServerSession(authOptions); - if (!session) throw new Error('Missing session'); - - const { user } = session; - - const { data } = await getCustomer({ telegramId: user?.telegramId }); - const customer = data.customers.at(0); - - if (!customer) throw new Error('Customer not found'); - - await updateCustomerProfile({ - data: input, - documentId: customer?.documentId, - }); -} diff --git a/apps/web/actions/services.ts b/apps/web/actions/services.ts deleted file mode 100644 index fa19552..0000000 --- a/apps/web/actions/services.ts +++ /dev/null @@ -1,19 +0,0 @@ -'use server'; -import { getProfile } from './profile'; -import * as api from '@repo/graphql/api/service'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '@repo/graphql/node_modules/@apollo/client/core'; -import { type GetServicesQueryVariables } from '@repo/graphql/types'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -export async function getServices(input?: GetServicesQueryVariables) { - const customer = await getProfile(); - - const filters = input || { filters: { master: { documentId: { eq: customer.documentId } } } }; - - return api.getServices(filters); -} - -export const getService = api.getService; diff --git a/apps/web/actions/session.ts b/apps/web/actions/session.ts new file mode 100644 index 0000000..e3f57b7 --- /dev/null +++ b/apps/web/actions/session.ts @@ -0,0 +1,13 @@ +'use server'; + +import { authOptions } from '@/config/auth'; +import { getServerSession } from 'next-auth/next'; + +export async function getSessionUser() { + const session = await getServerSession(authOptions); + const user = session?.user; + + if (!user?.telegramId) throw new Error('Missing session'); + + return user; +} diff --git a/apps/web/actions/slots.ts b/apps/web/actions/slots.ts deleted file mode 100644 index 19d7dff..0000000 --- a/apps/web/actions/slots.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use server'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '../../../packages/graphql/node_modules/@apollo/client/core'; -import { getProfile } from './profile'; -import { formatDate, formatTime } from '@/utils/date'; -import * as api from '@repo/graphql/api'; -import type * as GQL from '@repo/graphql/types'; - -type AddSlotInput = Omit; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -export async function addSlot(input: AddSlotInput) { - const customer = await getProfile(); - - return api.createSlot({ - ...input, - date: formatDate(input.date).db(), - master: customer?.documentId, - time_end: formatTime(input.time_end).db(), - time_start: formatTime(input.time_start).db(), - }); -} - -export async function getSlots(input: GQL.GetSlotsQueryVariables) { - const customer = await getProfile(); - - return api.getSlots({ - filters: { - master: { - documentId: { - eq: customer.documentId, - }, - }, - ...input.filters, - }, - }); -} - -export async function updateSlot(input: GQL.UpdateSlotMutationVariables) { - await getProfile(); - - return api.updateSlot({ - ...input, - data: { - ...input.data, - date: input.data?.date ? formatDate(input.data.date).db() : undefined, - time_end: input.data?.time_end ? formatTime(input.data.time_end).db() : undefined, - time_start: input.data?.time_start ? formatTime(input.data.time_start).db() : undefined, - }, - }); -} - -export const getSlot = api.getSlot; -export const deleteSlot = api.deleteSlot; diff --git a/apps/web/app/(auth)/browser/page.tsx b/apps/web/app/(auth)/browser/page.tsx index a989110..99666e7 100644 --- a/apps/web/app/(auth)/browser/page.tsx +++ b/apps/web/app/(auth)/browser/page.tsx @@ -1,5 +1,6 @@ /* eslint-disable promise/prefer-await-to-then */ 'use client'; + import { getTelegramUser } from '@/mocks/get-telegram-user'; import { LoadingSpinner } from '@repo/ui/components/ui/spinner'; import { signIn, useSession } from 'next-auth/react'; @@ -21,7 +22,7 @@ export default function Auth() { signIn('telegram', { callbackUrl: '/profile', redirect: false, - telegramId: String(user?.id), + telegramId: user?.id, }); }); } diff --git a/apps/web/app/(auth)/page.tsx b/apps/web/app/(auth)/page.tsx index 82767ed..2276c75 100644 --- a/apps/web/app/(auth)/page.tsx +++ b/apps/web/app/(auth)/page.tsx @@ -1,4 +1,5 @@ 'use client'; + import { useClientOnce } from '@/hooks/telegram'; import { isTMA } from '@telegram-apps/sdk-react'; import { redirect } from 'next/navigation'; diff --git a/apps/web/app/(auth)/telegram/page.tsx b/apps/web/app/(auth)/telegram/page.tsx index 36e24d1..fbfcbd2 100644 --- a/apps/web/app/(auth)/telegram/page.tsx +++ b/apps/web/app/(auth)/telegram/page.tsx @@ -1,5 +1,6 @@ /* eslint-disable promise/prefer-await-to-then */ 'use client'; + import { initData, isMiniAppDark, useSignal } from '@telegram-apps/sdk-react'; import { signIn, useSession } from 'next-auth/react'; import { useTheme } from 'next-themes'; @@ -28,7 +29,7 @@ function useAuth() { signIn('telegram', { callbackUrl: '/profile', redirect: false, - telegramId: String(initDataUser.id), + telegramId: initDataUser.id, }).then(() => redirect('/profile')); } }, [initDataUser?.id, status]); diff --git a/apps/web/app/(main)/profile/[telegramId]/page.tsx b/apps/web/app/(main)/profile/[telegramId]/page.tsx index dac16f6..8f1e663 100644 --- a/apps/web/app/(main)/profile/[telegramId]/page.tsx +++ b/apps/web/app/(main)/profile/[telegramId]/page.tsx @@ -7,7 +7,7 @@ type Props = { params: Promise<{ telegramId: string }> }; export default async function ProfilePage(props: Readonly) { const parameters = await props.params; - const { telegramId } = parameters; + const telegramId = Number.parseInt(parameters.telegramId, 10); const queryClient = new QueryClient(); diff --git a/apps/web/components/auth/update-profile.tsx b/apps/web/components/auth/update-profile.tsx index fae567e..be66aef 100644 --- a/apps/web/components/auth/update-profile.tsx +++ b/apps/web/components/auth/update-profile.tsx @@ -1,18 +1,21 @@ 'use client'; -import { useProfileMutation } from '@/hooks/profile'; + +import { useCustomerMutation } from '@/hooks/api/customers'; import { initData, useSignal } from '@telegram-apps/sdk-react'; import { useEffect, useState } from 'react'; export function UpdateProfile() { const initDataUser = useSignal(initData.user); - const { mutate: updateProfile } = useProfileMutation(); + const { mutate: updateProfile } = useCustomerMutation(); const [hasUpdated, setHasUpdated] = useState(false); useEffect(() => { if (!hasUpdated) { updateProfile({ - active: true, - photoUrl: initDataUser?.photoUrl || undefined, + data: { + active: true, + photoUrl: initDataUser?.photoUrl || undefined, + }, }); setHasUpdated(true); } diff --git a/apps/web/components/contacts/contacts-list.tsx b/apps/web/components/contacts/contacts-list.tsx index 3db173d..748424b 100644 --- a/apps/web/components/contacts/contacts-list.tsx +++ b/apps/web/components/contacts/contacts-list.tsx @@ -1,6 +1,7 @@ 'use client'; + import { LoadingSpinner } from '../common/spinner'; -import { useCustomerContacts } from '@/hooks/contacts'; +import { useCustomerContacts } from '@/hooks/api/contacts'; import * as GQL from '@repo/graphql/types'; import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; import Link from 'next/link'; diff --git a/apps/web/components/contacts/dropdown-filter.tsx b/apps/web/components/contacts/dropdown-filter.tsx index 62645f9..698d99a 100644 --- a/apps/web/components/contacts/dropdown-filter.tsx +++ b/apps/web/components/contacts/dropdown-filter.tsx @@ -1,4 +1,5 @@ 'use client'; + import { ContactsFilterContext, type FilterType } from '@/context/contacts-filter'; import { Button } from '@repo/ui/components/ui/button'; import { diff --git a/apps/web/components/navigation/bottom-nav.tsx b/apps/web/components/navigation/bottom-nav.tsx index 8866808..36d3ac0 100644 --- a/apps/web/components/navigation/bottom-nav.tsx +++ b/apps/web/components/navigation/bottom-nav.tsx @@ -1,4 +1,5 @@ 'use client'; + import { NavButton } from './components/nav-button'; import { BookOpen, Newspaper, PlusCircle, User, Users } from 'lucide-react'; import { usePathname } from 'next/navigation'; diff --git a/apps/web/components/navigation/components/back-button.tsx b/apps/web/components/navigation/components/back-button.tsx index 6486c48..8ac1865 100644 --- a/apps/web/components/navigation/components/back-button.tsx +++ b/apps/web/components/navigation/components/back-button.tsx @@ -1,4 +1,5 @@ 'use client'; + import { ArrowLeft } from 'lucide-react'; import { useRouter } from 'next/navigation'; diff --git a/apps/web/components/navigation/components/nav-button.tsx b/apps/web/components/navigation/components/nav-button.tsx index 9141105..52227c1 100644 --- a/apps/web/components/navigation/components/nav-button.tsx +++ b/apps/web/components/navigation/components/nav-button.tsx @@ -1,4 +1,5 @@ 'use client'; + import { Button } from '@repo/ui/components/ui/button'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; diff --git a/apps/web/components/navigation/header.tsx b/apps/web/components/navigation/header.tsx index 7876515..d8c70d9 100644 --- a/apps/web/components/navigation/header.tsx +++ b/apps/web/components/navigation/header.tsx @@ -1,4 +1,5 @@ 'use client'; + import { BackButton } from './components/back-button'; type Props = { title: string | undefined }; diff --git a/apps/web/components/orders/components/back-button.tsx b/apps/web/components/orders/components/back-button.tsx index df5f0f7..f2bb01d 100644 --- a/apps/web/components/orders/components/back-button.tsx +++ b/apps/web/components/orders/components/back-button.tsx @@ -1,5 +1,6 @@ 'use client'; -import { useOrderCreate } from '@/hooks/orders'; + +import { useOrderCreate } from '@/hooks/api/orders'; import { useOrderStore } from '@/stores/order'; import { Button } from '@repo/ui/components/ui/button'; diff --git a/apps/web/components/orders/components/contacts-grid/index.tsx b/apps/web/components/orders/components/contacts-grid/index.tsx index ec0612b..9b2a13b 100644 --- a/apps/web/components/orders/components/contacts-grid/index.tsx +++ b/apps/web/components/orders/components/contacts-grid/index.tsx @@ -1,14 +1,15 @@ 'use client'; + import { ContactsGridBase } from './components'; import { LoadingSpinner } from '@/components/common/spinner'; import { ContactsFilterProvider } from '@/context/contacts-filter'; -import { useCustomerContacts } from '@/hooks/contacts'; +import { useCustomerContacts } from '@/hooks/api/contacts'; import { useOrderStore } from '@/stores/order'; import { withContext } from '@/utils/context'; import { useEffect } from 'react'; export const MastersGrid = withContext(ContactsFilterProvider)(function () { - const { contacts, isLoading, setFilter } = useCustomerContacts({ includeSelf: true }); + const { contacts, isLoading, setFilter } = useCustomerContacts(); const masterId = useOrderStore((store) => store.masterId); const setMasterId = useOrderStore((store) => store.setMasterId); diff --git a/apps/web/components/orders/components/datetime-select/components/date-select.tsx b/apps/web/components/orders/components/datetime-select/components/date-select.tsx index 015cf5a..eff3db7 100644 --- a/apps/web/components/orders/components/datetime-select/components/date-select.tsx +++ b/apps/web/components/orders/components/datetime-select/components/date-select.tsx @@ -1,4 +1,5 @@ 'use client'; + import { useOrderStore } from '@/stores/order'; import { Calendar } from '@repo/ui/components/ui/calendar'; import dayjs from 'dayjs'; diff --git a/apps/web/components/orders/components/datetime-select/components/time-select.tsx b/apps/web/components/orders/components/datetime-select/components/time-select.tsx index 4866e47..da25224 100644 --- a/apps/web/components/orders/components/datetime-select/components/time-select.tsx +++ b/apps/web/components/orders/components/datetime-select/components/time-select.tsx @@ -1,6 +1,7 @@ /* eslint-disable canonical/id-match */ 'use client'; -import { useSlots } from '@/hooks/slots'; + +import { useSlotsQuery } from '@/hooks/api/slots'; import { useOrderStore } from '@/stores/order'; import { Enum_Slot_State, type SlotFieldsFragment } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; @@ -25,9 +26,20 @@ const generateTimeSlots = (slots: SlotFieldsFragment[]): Array<{ slotId: string; export function TimeSelect() { const masterId = useOrderStore((store) => store.masterId); const date = useOrderStore((store) => store.date); - const { data } = useSlots({ date, masterId }); + const { data: { slots } = {} } = useSlotsQuery({ + filters: { + date: { + eq: date, + }, + master: { + documentId: { + eq: masterId, + }, + }, + }, + }); - const openedSlots = data?.data.slots.filter((slot) => slot?.state === Enum_Slot_State.Open); + const openedSlots = slots?.filter((slot) => slot?.state === Enum_Slot_State.Open); const timeSlots = generateTimeSlots(openedSlots ? sift(openedSlots) : []); const morning = timeSlots.filter((time) => time.time.hour() < 12); diff --git a/apps/web/components/orders/components/next-button.tsx b/apps/web/components/orders/components/next-button.tsx index 081be1c..53bec02 100644 --- a/apps/web/components/orders/components/next-button.tsx +++ b/apps/web/components/orders/components/next-button.tsx @@ -1,4 +1,5 @@ 'use client'; + import { useOrderStore } from '@/stores/order'; import { Button } from '@repo/ui/components/ui/button'; diff --git a/apps/web/components/orders/components/result.tsx b/apps/web/components/orders/components/result.tsx index 195d40b..9f09837 100644 --- a/apps/web/components/orders/components/result.tsx +++ b/apps/web/components/orders/components/result.tsx @@ -1,4 +1,5 @@ 'use client'; + import { useOrderStore } from '@/stores/order'; import { Button } from '@repo/ui/components/ui/button'; import { Card, CardContent } from '@repo/ui/components/ui/card'; diff --git a/apps/web/components/orders/components/service-select.tsx b/apps/web/components/orders/components/service-select.tsx index e341c01..a3f0ceb 100644 --- a/apps/web/components/orders/components/service-select.tsx +++ b/apps/web/components/orders/components/service-select.tsx @@ -1,17 +1,28 @@ 'use client'; -import { useServicesQuery } from '@/hooks/services'; + +import { useServicesQuery } from '@/hooks/api/services'; import { useOrderStore } from '@/stores/order'; import { type ServiceFieldsFragment } from '@repo/graphql/types'; import { cn } from '@repo/ui/lib/utils'; export function ServiceSelect() { - const { data } = useServicesQuery(); + const masterId = useOrderStore((store) => store.masterId); + + const { data: { services } = {} } = useServicesQuery({ + filters: { + master: { + documentId: { + eq: masterId, + }, + }, + }, + }); + + if (!services?.length) return null; return (
- {data?.data.services.map( - (service) => service && , - )} + {services.map((service) => service && )}
); } diff --git a/apps/web/components/orders/components/submit-button.tsx b/apps/web/components/orders/components/submit-button.tsx index 35a660f..464fafb 100644 --- a/apps/web/components/orders/components/submit-button.tsx +++ b/apps/web/components/orders/components/submit-button.tsx @@ -1,5 +1,6 @@ 'use client'; -import { useOrderCreate } from '@/hooks/orders'; + +import { useOrderCreate } from '@/hooks/api/orders'; import { useOrderStore } from '@/stores/order'; import { Button } from '@repo/ui/components/ui/button'; import { LoadingSpinner } from '@repo/ui/components/ui/spinner'; @@ -14,7 +15,15 @@ export function SubmitButton() { const handleSubmit = () => { if (isDisabled) return; - createOrder({ clientId, date, serviceId, slotId, time }); + createOrder({ + input: { + client: clientId, + date, + services: [serviceId], + slot: slotId, + time_start: time, + }, + }); }; useEffect(() => { diff --git a/apps/web/components/orders/order-form.tsx b/apps/web/components/orders/order-form.tsx index 0292c13..778a15f 100644 --- a/apps/web/components/orders/order-form.tsx +++ b/apps/web/components/orders/order-form.tsx @@ -1,4 +1,5 @@ 'use client'; + import { LoadingSpinner } from '../common/spinner'; import { BackButton, diff --git a/apps/web/components/profile/components/checkbox-field.tsx b/apps/web/components/profile/components/checkbox-field.tsx index 522301b..07dcaf5 100644 --- a/apps/web/components/profile/components/checkbox-field.tsx +++ b/apps/web/components/profile/components/checkbox-field.tsx @@ -1,5 +1,6 @@ /* eslint-disable promise/prefer-await-to-then */ 'use client'; + import { Checkbox, type CheckboxProps } from '@repo/ui/components/ui/checkbox'; import { useState } from 'react'; import { useDebouncedCallback } from 'use-debounce'; diff --git a/apps/web/components/profile/components/text-field.tsx b/apps/web/components/profile/components/text-field.tsx index c458223..fd0b18a 100644 --- a/apps/web/components/profile/components/text-field.tsx +++ b/apps/web/components/profile/components/text-field.tsx @@ -1,5 +1,6 @@ /* eslint-disable promise/prefer-await-to-then */ 'use client'; + import { type CustomerInput } from '@repo/graphql/types'; import { Input } from '@repo/ui/components/ui/input'; import { Label } from '@repo/ui/components/ui/label'; diff --git a/apps/web/components/profile/data-card.tsx b/apps/web/components/profile/data-card.tsx index 739f36d..f13993f 100644 --- a/apps/web/components/profile/data-card.tsx +++ b/apps/web/components/profile/data-card.tsx @@ -1,15 +1,16 @@ 'use client'; + import { CardSectionHeader } from '../ui'; import { CheckboxWithText, DataField } from './components'; import { type ProfileProps } from './types'; -import { useProfileMutation, useProfileQuery } from '@/hooks/profile'; +import { useCustomerMutation, useCustomerQuery } from '@/hooks/api/customers'; import { Enum_Customer_Role as Role } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; import { Card } from '@repo/ui/components/ui/card'; import Link from 'next/link'; export function ContactDataCard({ telegramId }: Readonly) { - const { data: customer } = useProfileQuery({ telegramId }); + const { data: { customer } = {} } = useCustomerQuery({ telegramId }); if (!customer) return null; @@ -30,8 +31,8 @@ export function ContactDataCard({ telegramId }: Readonly) { } export function ProfileDataCard() { - const { data: customer } = useProfileQuery(); - const { mutate: updateProfile } = useProfileMutation(); + const { data: { customer } = {} } = useCustomerQuery(); + const { mutate: updateCustomer } = useCustomerMutation(); if (!customer) return null; @@ -43,14 +44,18 @@ export function ProfileDataCard() { fieldName="name" id="name" label="Имя" - onChange={updateProfile} + onChange={({ name }) => updateCustomer({ data: { name } })} value={customer?.name ?? ''} /> updateProfile({ role: checked ? Role.Master : Role.Client })} + onChange={(checked) => + updateCustomer({ + data: { role: checked ? Role.Master : Role.Client }, + }) + } text="Быть мастером" /> diff --git a/apps/web/components/profile/links-card.tsx b/apps/web/components/profile/links-card.tsx index 0a7aa63..b6f8262 100644 --- a/apps/web/components/profile/links-card.tsx +++ b/apps/web/components/profile/links-card.tsx @@ -1,12 +1,13 @@ /* eslint-disable canonical/id-match */ 'use client'; + import { LinkButton } from './components'; import { type ProfileProps } from './types'; -import { useProfileQuery } from '@/hooks/profile'; +import { useCustomerQuery } from '@/hooks/api/customers'; import { Enum_Customer_Role } from '@repo/graphql/types'; export function LinksCard({ telegramId }: Readonly) { - const { data: customer } = useProfileQuery({ telegramId }); + const { data: { customer } = {} } = useCustomerQuery({ telegramId }); const isMaster = customer?.role === Enum_Customer_Role.Master; diff --git a/apps/web/components/profile/person-card.tsx b/apps/web/components/profile/person-card.tsx index 0d15d9e..f7ac660 100644 --- a/apps/web/components/profile/person-card.tsx +++ b/apps/web/components/profile/person-card.tsx @@ -1,12 +1,13 @@ 'use client'; + import { LoadingSpinner } from '../common/spinner'; import { type ProfileProps } from './types'; -import { useProfileQuery } from '@/hooks/profile'; +import { useCustomerQuery } from '@/hooks/api/customers'; import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; import { Card } from '@repo/ui/components/ui/card'; export function PersonCard({ telegramId }: Readonly) { - const { data: customer, isLoading } = useProfileQuery({ telegramId }); + const { data: { customer } = {}, isLoading } = useCustomerQuery({ telegramId }); if (isLoading || !customer) return ( diff --git a/apps/web/components/profile/types.tsx b/apps/web/components/profile/types.tsx index 974e69f..3090f9a 100644 --- a/apps/web/components/profile/types.tsx +++ b/apps/web/components/profile/types.tsx @@ -1,3 +1,3 @@ export type ProfileProps = { - readonly telegramId?: string; + readonly telegramId?: number; }; diff --git a/apps/web/components/schedule/calendar.tsx b/apps/web/components/schedule/calendar.tsx index 4a024b8..be9fa86 100644 --- a/apps/web/components/schedule/calendar.tsx +++ b/apps/web/components/schedule/calendar.tsx @@ -1,4 +1,5 @@ 'use client'; + import { ScheduleContext } from '@/context/schedule'; import { Calendar } from '@repo/ui/components/ui/calendar'; import dayjs from 'dayjs'; diff --git a/apps/web/components/schedule/components/order-card.tsx b/apps/web/components/schedule/components/order-card.tsx index c75c39f..346832a 100644 --- a/apps/web/components/schedule/components/order-card.tsx +++ b/apps/web/components/schedule/components/order-card.tsx @@ -1,8 +1,9 @@ /* eslint-disable canonical/id-match */ 'use client'; + import { type OrderClient, type OrderComponentProps } from '../types'; import { ReadonlyTimeRange } from './time-range'; -import { useOrderQuery } from '@/hooks/orders'; +import { useOrderQuery } from '@/hooks/api/orders'; import { Enum_Order_State } from '@repo/graphql/types'; import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; import { Badge } from '@repo/ui/components/ui/badge'; @@ -10,8 +11,7 @@ import { cn } from '@repo/ui/lib/utils'; import Link from 'next/link'; export function OrderCard({ documentId }: Readonly) { - const { data } = useOrderQuery({ documentId }); - const order = data?.data?.order; + const { data: { order } = {} } = useOrderQuery({ documentId }); if (!order) return null; diff --git a/apps/web/components/schedule/components/slot-card.tsx b/apps/web/components/schedule/components/slot-card.tsx index 4d19768..da76edb 100644 --- a/apps/web/components/schedule/components/slot-card.tsx +++ b/apps/web/components/schedule/components/slot-card.tsx @@ -1,7 +1,8 @@ /* eslint-disable canonical/id-match */ 'use client'; + import { ReadonlyTimeRange } from './time-range'; -import { useSlotQuery } from '@/hooks/slots'; +import { useSlotQuery } from '@/hooks/api/slots'; import { Enum_Slot_State, type SlotFieldsFragment } from '@repo/graphql/types'; import { Badge } from '@repo/ui/components/ui/badge'; import { cn } from '@repo/ui/lib/utils'; @@ -18,8 +19,7 @@ export function SlotCard(props: Readonly) { const pathname = usePathname(); const { documentId } = props; - const { data } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); const ordersNumber = slot?.orders?.length; const hasOrders = Boolean(ordersNumber); diff --git a/apps/web/components/schedule/components/slot-date.tsx b/apps/web/components/schedule/components/slot-date.tsx index 8493aca..d967f69 100644 --- a/apps/web/components/schedule/components/slot-date.tsx +++ b/apps/web/components/schedule/components/slot-date.tsx @@ -1,11 +1,11 @@ 'use client'; + import { type SlotComponentProps } from '../types'; -import { useSlotQuery } from '@/hooks/slots'; -import { formatDate } from '@/utils/date'; +import { useSlotQuery } from '@/hooks/api/slots'; +import { formatDate } from '@repo/graphql/utils/datetime-format'; export function SlotDate({ documentId }: Readonly) { - const { data } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); if (!slot) return null; diff --git a/apps/web/components/schedule/components/slot-time.tsx b/apps/web/components/schedule/components/slot-time.tsx index d0b0c62..e0b843c 100644 --- a/apps/web/components/schedule/components/slot-time.tsx +++ b/apps/web/components/schedule/components/slot-time.tsx @@ -1,9 +1,10 @@ /* eslint-disable react/jsx-no-bind */ 'use client'; + import { ScheduleTimeContext } from '../context'; import { type SlotComponentProps } from '../types'; import { EditableTimeRangeForm, ReadonlyTimeRange } from './time-range'; -import { useSlotMutation, useSlotQuery } from '@/hooks/slots'; +import { useSlotMutation, useSlotQuery } from '@/hooks/api/slots'; import { Button } from '@repo/ui/components/ui/button'; import { PencilLine } from 'lucide-react'; import { use, useEffect } from 'react'; @@ -19,8 +20,7 @@ function SlotTimeEditForm({ documentId }: Readonly) { use(ScheduleTimeContext); const { isPending: isMutationPending, mutate: updateSlot } = useSlotMutation({ documentId }); - const { data, isPending: isQueryPending } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {}, isPending: isQueryPending } = useSlotQuery({ documentId }); const isPending = isMutationPending || isQueryPending; @@ -32,7 +32,7 @@ function SlotTimeEditForm({ documentId }: Readonly) { }, [editMode, setEndTime, setStartTime, slot]); function handleSubmit() { - updateSlot({ data: { time_end: endTime, time_start: startTime }, documentId }); + updateSlot({ data: { time_end: endTime, time_start: startTime } }); resetTime(); setEditMode(false); } @@ -46,11 +46,10 @@ function SlotTimeEditForm({ documentId }: Readonly) { ); } -function SlotTimeReadonly(props: Readonly) { +function SlotTimeReadonly({ documentId }: Readonly) { const { setEditMode } = use(ScheduleTimeContext); - const { data } = useSlotQuery(props); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); if (!slot) return null; diff --git a/apps/web/components/schedule/components/time-range.tsx b/apps/web/components/schedule/components/time-range.tsx index 6041ebd..129357d 100644 --- a/apps/web/components/schedule/components/time-range.tsx +++ b/apps/web/components/schedule/components/time-range.tsx @@ -1,6 +1,7 @@ 'use client'; + import { ScheduleTimeContext } from '../context'; -import { formatTime } from '@/utils/date'; +import { formatTime } from '@repo/graphql/utils/datetime-format'; import { Input } from '@repo/ui/components/ui/input'; import { cn } from '@repo/ui/lib/utils'; import { type FormEvent, type PropsWithChildren, use } from 'react'; diff --git a/apps/web/components/schedule/context/index.tsx b/apps/web/components/schedule/context/index.tsx index 13d0474..1629846 100644 --- a/apps/web/components/schedule/context/index.tsx +++ b/apps/web/components/schedule/context/index.tsx @@ -1,4 +1,5 @@ 'use client'; + import { createContext, type Dispatch, diff --git a/apps/web/components/schedule/day-slot-add-form.tsx b/apps/web/components/schedule/day-slot-add-form.tsx index ccc593b..e576c8b 100644 --- a/apps/web/components/schedule/day-slot-add-form.tsx +++ b/apps/web/components/schedule/day-slot-add-form.tsx @@ -1,9 +1,9 @@ /* eslint-disable canonical/id-match */ 'use client'; + import { EditableTimeRangeForm } from './components/time-range'; import { ScheduleTimeContext, ScheduleTimeContextProvider } from './context'; -import { ScheduleContext } from '@/context/schedule'; -import { useSlotAdd } from '@/hooks/slots'; +import { useSlotCreate } from '@/hooks/api/slots'; import { withContext } from '@/utils/context'; import { Enum_Slot_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; @@ -13,18 +13,17 @@ import { type FormEvent, use } from 'react'; export const DaySlotAddForm = withContext(ScheduleTimeContextProvider)(function () { const { endTime, resetTime, startTime } = use(ScheduleTimeContext); - const { selectedDate } = use(ScheduleContext); - - const { isPending, mutate: addSlot } = useSlotAdd({ date: selectedDate }); + const { isPending, mutate: addSlot } = useSlotCreate(); const handleSubmit = (event: FormEvent) => { event.preventDefault(); if (startTime && endTime) { addSlot({ - date: selectedDate, - state: Enum_Slot_State.Open, - time_end: endTime, - time_start: startTime, + input: { + state: Enum_Slot_State.Open, + time_end: endTime, + time_start: startTime, + }, }); resetTime(); diff --git a/apps/web/components/schedule/day-slots-list.tsx b/apps/web/components/schedule/day-slots-list.tsx index caf06b2..a2bac63 100644 --- a/apps/web/components/schedule/day-slots-list.tsx +++ b/apps/web/components/schedule/day-slots-list.tsx @@ -1,15 +1,17 @@ 'use client'; + import { SlotCard } from './components/slot-card'; import { DaySlotAddForm } from './day-slot-add-form'; import { LoadingSpinner } from '@/components/common/spinner'; import { ScheduleContext } from '@/context/schedule'; -import { useSlots } from '@/hooks/slots'; +import { useSlotsQuery } from '@/hooks/api/slots'; import { use } from 'react'; export function DaySlotsList() { const { selectedDate } = use(ScheduleContext); - const { data, isLoading } = useSlots({ date: selectedDate }); - const slots = data?.data.slots; + const { data: { slots } = {}, isLoading } = useSlotsQuery({ + filters: { date: { eq: selectedDate } }, + }); if (isLoading) return ; diff --git a/apps/web/components/schedule/slot-buttons.tsx b/apps/web/components/schedule/slot-buttons.tsx index c9ce691..587a3b2 100644 --- a/apps/web/components/schedule/slot-buttons.tsx +++ b/apps/web/components/schedule/slot-buttons.tsx @@ -1,37 +1,33 @@ /* eslint-disable react/jsx-no-bind */ /* eslint-disable canonical/id-match */ 'use client'; + import { type SlotComponentProps } from './types'; -import { ScheduleContext } from '@/context/schedule'; -import { useSlotDelete, useSlotMutation, useSlotQuery } from '@/hooks/slots'; +import { useSlotDelete, useSlotMutation, useSlotQuery } from '@/hooks/api/slots'; import { Enum_Slot_State } from '@repo/graphql/types'; import { Button } from '@repo/ui/components/ui/button'; import { useRouter } from 'next/navigation'; -import { use } from 'react'; export function SlotButtons({ documentId }: Readonly) { - const { data } = useSlotQuery({ documentId }); + const { data: { slot } = {} } = useSlotQuery({ documentId }); const { mutate: updateSlot } = useSlotMutation({ documentId }); - const { selectedDate } = use(ScheduleContext); - const { mutate: deleteSlot } = useSlotDelete({ date: selectedDate, documentId }); + const { mutate: deleteSlot } = useSlotDelete({ documentId }); const router = useRouter(); - const slot = data?.data?.slot; - if (!slot) return null; const isOpened = slot?.state === Enum_Slot_State.Open; const isClosed = slot?.state === Enum_Slot_State.Closed; function handleOpenSlot() { - return updateSlot({ data: { state: Enum_Slot_State.Open }, documentId }); + return updateSlot({ data: { state: Enum_Slot_State.Open } }); } function handleCloseSlot() { - return updateSlot({ data: { state: Enum_Slot_State.Closed }, documentId }); + return updateSlot({ data: { state: Enum_Slot_State.Closed } }); } function handleDeleteSlot() { diff --git a/apps/web/components/schedule/slot-datetime.tsx b/apps/web/components/schedule/slot-datetime.tsx index a76c0fc..9fc54b7 100644 --- a/apps/web/components/schedule/slot-datetime.tsx +++ b/apps/web/components/schedule/slot-datetime.tsx @@ -1,4 +1,5 @@ 'use client'; + import { SlotDate } from './components/slot-date'; import { SlotTime } from './components/slot-time'; import { ScheduleTimeContextProvider } from './context'; diff --git a/apps/web/components/schedule/slot-orders-list.tsx b/apps/web/components/schedule/slot-orders-list.tsx index f84226d..61d01f7 100644 --- a/apps/web/components/schedule/slot-orders-list.tsx +++ b/apps/web/components/schedule/slot-orders-list.tsx @@ -1,11 +1,11 @@ 'use client'; + import { OrderCard } from './components/order-card'; import { type SlotComponentProps } from './types'; -import { useSlotQuery } from '@/hooks/slots'; +import { useSlotQuery } from '@/hooks/api/slots'; export function SlotOrdersList({ documentId }: Readonly) { - const { data } = useSlotQuery({ documentId }); - const slot = data?.data?.slot; + const { data: { slot } = {} } = useSlotQuery({ documentId }); if (!slot) return null; diff --git a/apps/web/config/auth.ts b/apps/web/config/auth.ts index 3846dcb..d7e9522 100644 --- a/apps/web/config/auth.ts +++ b/apps/web/config/auth.ts @@ -12,7 +12,7 @@ export const authOptions: AuthOptions = { }, async session({ session, token }) { if (token?.id && session?.user) { - session.user.telegramId = token.id as string; + session.user.telegramId = token.id as number; } return session; diff --git a/apps/web/context/contacts-filter.tsx b/apps/web/context/contacts-filter.tsx index 9a30bc5..df7125d 100644 --- a/apps/web/context/contacts-filter.tsx +++ b/apps/web/context/contacts-filter.tsx @@ -1,4 +1,5 @@ 'use client'; + import { createContext, type PropsWithChildren, useMemo, useState } from 'react'; export type FilterType = 'all' | 'clients' | 'masters'; diff --git a/apps/web/context/schedule.tsx b/apps/web/context/schedule.tsx index 97e747a..a8d9bd1 100644 --- a/apps/web/context/schedule.tsx +++ b/apps/web/context/schedule.tsx @@ -1,4 +1,5 @@ 'use client'; + import { createContext, useMemo, useState } from 'react'; type ContextType = { diff --git a/apps/web/hooks/contacts/index.ts b/apps/web/hooks/api/contacts/index.ts similarity index 100% rename from apps/web/hooks/contacts/index.ts rename to apps/web/hooks/api/contacts/index.ts diff --git a/apps/web/hooks/api/contacts/query.ts b/apps/web/hooks/api/contacts/query.ts new file mode 100644 index 0000000..afe8e97 --- /dev/null +++ b/apps/web/hooks/api/contacts/query.ts @@ -0,0 +1,23 @@ +import { getClients, getMasters } from '@/actions/api/customers'; +import { useQuery } from '@tanstack/react-query'; +import { useSession } from 'next-auth/react'; + +export const useClientsQuery = (props?: Parameters[0]) => { + const { data: session } = useSession(); + const telegramId = props?.telegramId || session?.user?.telegramId; + + return useQuery({ + queryFn: () => getClients({ telegramId }), + queryKey: ['customer', 'telegramId', telegramId, 'clients'], + }); +}; + +export const useMastersQuery = (props?: Parameters[0]) => { + const { data: session } = useSession(); + const telegramId = props?.telegramId || session?.user?.telegramId; + + return useQuery({ + queryFn: () => getMasters({ telegramId }), + queryKey: ['customer', 'telegramId', telegramId, 'masters'], + }); +}; diff --git a/apps/web/hooks/contacts/use-customer-contacts.ts b/apps/web/hooks/api/contacts/use-customer-contacts.ts similarity index 62% rename from apps/web/hooks/contacts/use-customer-contacts.ts rename to apps/web/hooks/api/contacts/use-customer-contacts.ts index 92b300d..43dd5d7 100644 --- a/apps/web/hooks/contacts/use-customer-contacts.ts +++ b/apps/web/hooks/api/contacts/use-customer-contacts.ts @@ -1,19 +1,12 @@ -/* eslint-disable canonical/id-match */ 'use client'; -import { useProfileQuery } from '../profile'; + import { useClientsQuery, useMastersQuery } from './query'; import { ContactsFilterContext } from '@/context/contacts-filter'; -import { Enum_Customer_Role } from '@repo/graphql/types'; import { sift } from 'radash'; import { use, useEffect, useMemo } from 'react'; -type Parameters_ = { - includeSelf: boolean; -}; - -export function useCustomerContacts(parameters?: Parameters_) { +export function useCustomerContacts() { const { filter, setFilter } = use(ContactsFilterContext); - const { data: customer, isLoading: isLoadingProfile } = useProfileQuery(); const { data: clientsData, @@ -27,14 +20,10 @@ export function useCustomerContacts(parameters?: Parameters_) { refetch: refetchMasters, } = useMastersQuery(); - const clients = clientsData?.clients || []; - let masters = mastersData?.masters || []; + const clients = clientsData?.customers?.at(0)?.clients || []; + const masters = mastersData?.customers?.at(0)?.masters || []; - if (parameters?.includeSelf && customer?.role === Enum_Customer_Role.Master) { - masters = [{ ...customer, name: 'Я' }, ...masters]; - } - - const isLoading = isLoadingClients || isLoadingMasters || isLoadingProfile; + const isLoading = isLoadingClients || isLoadingMasters; useEffect(() => { if (filter === 'clients') { diff --git a/apps/web/hooks/api/customers.ts b/apps/web/hooks/api/customers.ts new file mode 100644 index 0000000..32f3b3f --- /dev/null +++ b/apps/web/hooks/api/customers.ts @@ -0,0 +1,35 @@ +'use client'; + +import { getCustomer, updateCustomer } from '@/actions/api/customers'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useSession } from 'next-auth/react'; + +export const useCustomerQuery = (variables?: Parameters[0]) => { + const { data: session } = useSession(); + const telegramId = variables?.telegramId || session?.user?.telegramId; + + return useQuery({ + enabled: Boolean(telegramId), + queryFn: () => getCustomer({ telegramId }), + queryKey: ['customer', telegramId], + }); +}; + +export const useCustomerMutation = () => { + const { data: session } = useSession(); + const telegramId = session?.user?.telegramId; + const queryClient = useQueryClient(); + + const handleOnSuccess = () => { + if (!telegramId) return; + + queryClient.invalidateQueries({ + queryKey: ['customer', telegramId], + }); + }; + + return useMutation({ + mutationFn: updateCustomer, + onSuccess: handleOnSuccess, + }); +}; diff --git a/apps/web/hooks/api/orders.ts b/apps/web/hooks/api/orders.ts new file mode 100644 index 0000000..4f5bffd --- /dev/null +++ b/apps/web/hooks/api/orders.ts @@ -0,0 +1,16 @@ +'use client'; + +import { createOrder, getOrder } from '@/actions/api/orders'; +import { useMutation, useQuery } from '@tanstack/react-query'; + +export const useOrderQuery = ({ documentId }: Parameters[0]) => + useQuery({ + queryFn: () => getOrder({ documentId }), + queryKey: ['order', documentId], + }); + +export const useOrderCreate = () => + useMutation({ + mutationFn: createOrder, + mutationKey: ['order', 'create'], + }); diff --git a/apps/web/hooks/api/services.ts b/apps/web/hooks/api/services.ts new file mode 100644 index 0000000..8fc7a54 --- /dev/null +++ b/apps/web/hooks/api/services.ts @@ -0,0 +1,18 @@ +'use client'; + +import { getService, getServices } from '@/actions/api/services'; +import { useQuery } from '@tanstack/react-query'; + +export const useServicesQuery = (input: Parameters[0]) => { + return useQuery({ + queryFn: () => getServices(input), + queryKey: ['services', input], + }); +}; + +export const useServiceQuery = (input: Parameters[0]) => { + return useQuery({ + queryFn: () => getService(input), + queryKey: ['service', input.documentId], + }); +}; diff --git a/apps/web/hooks/api/slots.ts b/apps/web/hooks/api/slots.ts new file mode 100644 index 0000000..ec7cbd5 --- /dev/null +++ b/apps/web/hooks/api/slots.ts @@ -0,0 +1,87 @@ +'use client'; + +import { useCustomerQuery } from './customers'; +import { createSlot, deleteSlot, getSlot, getSlots, updateSlot } from '@/actions/api/slots'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +export const useSlotsQuery = (variables: Parameters[0]) => { + const { data: { customer } = {} } = useCustomerQuery(); + + const masterId = variables.filters?.master?.documentId?.eq || customer?.documentId; + const date = variables.filters?.date?.eq; + + return useQuery({ + queryFn: () => getSlots(variables), + queryKey: ['slots', { date: date?.toISOString(), masterId }], + }); +}; + +export const useSlotQuery = (variables: Parameters[0]) => { + const { documentId } = variables; + + return useQuery({ + queryFn: () => getSlot(variables), + queryKey: ['slot', documentId], + }); +}; + +export const useSlotMutation = ({ + documentId, +}: Pick[0], 'documentId'>) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ data }: Pick[0], 'data'>) => + updateSlot({ data, documentId }), + mutationKey: ['slot', 'update', documentId], + onSuccess: () => { + if (documentId) { + queryClient.invalidateQueries({ queryKey: ['slot', documentId] }); + } + }, + }); +}; + +export const useSlotCreate = () => { + const { data: { customer } = {} } = useCustomerQuery(); + const masterId = customer?.documentId; + + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createSlot, + mutationKey: ['slot', 'create'], + onSuccess: masterId + ? (data) => { + queryClient.invalidateQueries({ + queryKey: ['slots', { date: data?.createSlot?.date?.toISOString(), masterId }], + }); + } + : undefined, + }); +}; + +export const useSlotDelete = ({ documentId }: Parameters[0]) => { + const { data: { slot } = {} } = useSlotQuery({ documentId }); + + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => deleteSlot({ documentId }), + mutationKey: ['slot', 'delete', documentId], + onSuccess: () => { + const date = slot?.date; + const masterId = slot?.master; + + if (date && masterId) { + queryClient.invalidateQueries({ + queryKey: ['slots', { date: date?.toISOString(), masterId }], + }); + + queryClient.invalidateQueries({ + queryKey: ['slot', documentId], + }); + } + }, + }); +}; diff --git a/apps/web/hooks/contacts/query.ts b/apps/web/hooks/contacts/query.ts deleted file mode 100644 index 7c4c397..0000000 --- a/apps/web/hooks/contacts/query.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getClients, getMasters } from '@/actions/contacts'; -import { useQuery } from '@tanstack/react-query'; - -export const useClientsQuery = () => - useQuery({ enabled: false, queryFn: getClients, queryKey: ['contacts', 'clients', 'get'] }); - -export const useMastersQuery = () => - useQuery({ enabled: false, queryFn: getMasters, queryKey: ['contacts', 'masters', 'get'] }); diff --git a/apps/web/hooks/orders/index.ts b/apps/web/hooks/orders/index.ts deleted file mode 100644 index bdd0957..0000000 --- a/apps/web/hooks/orders/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -'use client'; -import { createOrder, getOrder } from '@/actions/orders'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '@repo/graphql/node_modules/@apollo/client/core'; -import { useMutation, useQuery } from '@tanstack/react-query'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -type Props = { - documentId: string; -}; - -export const useOrderQuery = ({ documentId }: Props) => - useQuery({ - queryFn: () => getOrder({ documentId }), - queryKey: ['orders', 'get', documentId], - }); - -export const useOrderCreate = () => - useMutation({ - mutationFn: createOrder, - mutationKey: ['orders', 'create'], - }); diff --git a/apps/web/hooks/profile/index.ts b/apps/web/hooks/profile/index.ts deleted file mode 100644 index 45bc79b..0000000 --- a/apps/web/hooks/profile/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -'use client'; -import { getProfile, updateProfile } from '@/actions/profile'; -import { type ProfileProps } from '@/components/profile/types'; -import { useMutation, useQuery } from '@tanstack/react-query'; - -export const useProfileQuery = (props?: ProfileProps) => { - const telegramId = props?.telegramId; - - return useQuery({ - queryFn: () => getProfile({ telegramId }), - queryKey: telegramId ? ['profile', 'telegramId', telegramId, 'get'] : ['profile', 'get'], - }); -}; - -export const useProfileMutation = () => { - const { refetch } = useProfileQuery(); - - return useMutation({ - mutationFn: updateProfile, - mutationKey: ['profile', 'update'], - onSuccess: () => refetch(), - }); -}; diff --git a/apps/web/hooks/services/index.ts b/apps/web/hooks/services/index.ts deleted file mode 100644 index c8e8475..0000000 --- a/apps/web/hooks/services/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; -import { getServices } from '@/actions/services'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '@repo/graphql/node_modules/@apollo/client/core'; -import { type GetServicesQueryVariables } from '@repo/graphql/types'; -import { useQuery } from '@tanstack/react-query'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -export const useServicesQuery = (input?: GetServicesQueryVariables) => - useQuery({ - queryFn: () => getServices(input), - queryKey: ['services', 'list'], - }); diff --git a/apps/web/hooks/slots/index.ts b/apps/web/hooks/slots/index.ts deleted file mode 100644 index eacc4c0..0000000 --- a/apps/web/hooks/slots/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -'use client'; -import { addSlot, deleteSlot, getSlot, getSlots, updateSlot } from '@/actions/slots'; -import { formatDate } from '@/utils/date'; -// eslint-disable-next-line sonarjs/no-internal-api-use -import type * as ApolloTypes from '@repo/graphql/node_modules/@apollo/client/core'; -import { useMutation, useQuery } from '@tanstack/react-query'; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type FixTypescriptCringe = ApolloTypes.FetchResult; - -type SlotAddInput = { - date: Date; -}; - -type SlotMutationInput = { - documentId: string; -}; - -type SlotQueryInput = { - date: Date; - masterId?: null | string; -}; - -export const useSlots = ({ date, masterId }: SlotQueryInput) => { - return useQuery({ - queryFn: () => - getSlots({ - filters: { - date: { eq: formatDate(date).db() }, - master: masterId ? { documentId: { eq: masterId } } : undefined, - }, - }), - queryKey: ['slots', 'master', masterId, 'list', date], - }); -}; - -export const useSlotQuery = ({ documentId }: SlotMutationInput) => - useQuery({ - queryFn: () => getSlot({ documentId }), - queryKey: ['slots', 'get', documentId], - }); - -export const useSlotMutation = ({ documentId }: SlotMutationInput) => { - const { refetch } = useSlotQuery({ documentId }); - - return useMutation({ - mutationFn: updateSlot, - mutationKey: ['slots', 'update', documentId], - onSuccess: () => refetch(), - }); -}; - -export const useSlotAdd = ({ date }: SlotAddInput) => { - const { refetch } = useSlots({ date }); - - return useMutation({ - mutationFn: addSlot, - mutationKey: ['slots', 'add'], - onSuccess: () => refetch(), - }); -}; - -export const useSlotDelete = ({ date, documentId }: SlotAddInput & SlotMutationInput) => { - const { refetch } = useSlots({ date }); - - return useMutation({ - mutationFn: () => deleteSlot({ documentId }), - mutationKey: ['slots', 'delete', documentId], - onSuccess: () => refetch(), - }); -}; diff --git a/apps/web/mocks/get-telegram-user.ts b/apps/web/mocks/get-telegram-user.ts index 1516fd5..f05cb73 100644 --- a/apps/web/mocks/get-telegram-user.ts +++ b/apps/web/mocks/get-telegram-user.ts @@ -5,7 +5,7 @@ import { env } from '@/config/env'; export async function getTelegramUser() { if (process.env.NODE_ENV !== 'production') return { - id: env.__DEV_TELEGRAM_ID, + id: Number.parseInt(env.__DEV_TELEGRAM_ID, 10), }; return null; diff --git a/apps/web/package.json b/apps/web/package.json index c76fca5..4db2048 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,7 +17,7 @@ "@repo/ui": "workspace:*", "@tanstack/react-query": "^5.64.1", "@telegram-apps/sdk-react": "^2.0.19", - "dayjs": "^1.11.13", + "dayjs": "catalog:", "graphql": "catalog:", "lucide-react": "catalog:", "next": "15.3.0", diff --git a/apps/web/providers/auth.tsx b/apps/web/providers/auth.tsx index 2027d02..88520aa 100644 --- a/apps/web/providers/auth.tsx +++ b/apps/web/providers/auth.tsx @@ -1,4 +1,5 @@ 'use client'; + import { SessionProvider } from 'next-auth/react'; export function AuthProvider({ children }: { readonly children: React.ReactNode }) { diff --git a/apps/web/providers/telegram.tsx b/apps/web/providers/telegram.tsx index 999e97f..2be20d6 100644 --- a/apps/web/providers/telegram.tsx +++ b/apps/web/providers/telegram.tsx @@ -1,5 +1,6 @@ /* eslint-disable sonarjs/function-return-type */ 'use client'; + import { useClientOnce, useDidMount } from '@/hooks/telegram'; import { setLocale } from '@/utils/i18n/locale'; import { init } from '@/utils/telegram/init'; diff --git a/apps/web/providers/theme-provider.tsx b/apps/web/providers/theme-provider.tsx index dd140f5..ad9f335 100644 --- a/apps/web/providers/theme-provider.tsx +++ b/apps/web/providers/theme-provider.tsx @@ -1,4 +1,5 @@ 'use client'; + import { ThemeProvider as NextThemesProvider } from 'next-themes'; import { type ComponentProps, useEffect, useState } from 'react'; diff --git a/apps/web/stores/order/context.tsx b/apps/web/stores/order/context.tsx index 2800c31..cbb9ae5 100644 --- a/apps/web/stores/order/context.tsx +++ b/apps/web/stores/order/context.tsx @@ -1,4 +1,5 @@ 'use client'; + import { createOrderStore } from './store'; import { createContext, type PropsWithChildren, useRef } from 'react'; diff --git a/apps/web/stores/order/hooks.tsx b/apps/web/stores/order/hooks.tsx index b6091f3..8e830a7 100644 --- a/apps/web/stores/order/hooks.tsx +++ b/apps/web/stores/order/hooks.tsx @@ -1,8 +1,9 @@ /* eslint-disable canonical/id-match */ 'use client'; + import { OrderStoreContext } from './context'; import { type OrderStore, type Steps } from './types'; -import { useProfileQuery } from '@/hooks/profile'; +import { useCustomerQuery } from '@/hooks/api/customers'; import { Enum_Customer_Role } from '@repo/graphql/types'; import { useContext, useEffect } from 'react'; import { useStore } from 'zustand'; @@ -28,7 +29,7 @@ export const MASTER_STEPS: Steps[] = STEPS.filter((step) => step !== 'master-sel export const CLIENT_STEPS: Steps[] = STEPS.filter((step) => step !== 'client-select'); export function useInitOrderStore() { - const { data } = useProfileQuery(); + const { data: { customer } = {} } = useCustomerQuery(); const setMasterId = useOrderStore((store) => store.setMasterId); const setClientId = useOrderStore((store) => store.setClientId); @@ -36,14 +37,14 @@ export function useInitOrderStore() { const setStepsSequence = useOrderStore((store) => store._setStepSequence); useEffect(() => { - const role = data?.role; + const role = customer?.role; - if (role === Enum_Customer_Role.Master && data) { - setMasterId(data?.documentId); + if (role === Enum_Customer_Role.Master && customer) { + setMasterId(customer?.documentId); } - if (role === Enum_Customer_Role.Client && data) { - setClientId(data?.documentId); + if (role === Enum_Customer_Role.Client && customer) { + setClientId(customer?.documentId); } const steps = role === Enum_Customer_Role.Master ? MASTER_STEPS : CLIENT_STEPS; @@ -51,5 +52,5 @@ export function useInitOrderStore() { setStepsSequence(steps); setStep(initialStep); - }, [data, setClientId, setMasterId, setStep, setStepsSequence]); + }, [customer, setClientId, setMasterId, setStep, setStepsSequence]); } diff --git a/apps/web/types/next-auth.d.ts b/apps/web/types/next-auth.d.ts index 9bf0e89..4dcef7c 100644 --- a/apps/web/types/next-auth.d.ts +++ b/apps/web/types/next-auth.d.ts @@ -4,11 +4,11 @@ import { type DefaultSession } from 'next-auth'; declare module 'next-auth' { interface Session extends DefaultSession { user?: { - telegramId?: null | string; + telegramId?: null | number; }; } interface User extends DefaultUser { - telegramId?: null | string; + telegramId?: null | number; } } diff --git a/packages/graphql/api/base.ts b/packages/graphql/api/base.ts new file mode 100644 index 0000000..f2531bd --- /dev/null +++ b/packages/graphql/api/base.ts @@ -0,0 +1,15 @@ +type CustomerProfile = { + telegramId: number; +}; + +export class BaseService { + protected customer: CustomerProfile; + + constructor(customer: CustomerProfile) { + if (!customer?.telegramId) { + throw new Error('Invalid customer profile: telegramId required'); + } + + this.customer = customer; + } +} diff --git a/packages/graphql/api/customer.ts b/packages/graphql/api/customer.ts deleted file mode 100644 index 024a0de..0000000 --- a/packages/graphql/api/customer.ts +++ /dev/null @@ -1,108 +0,0 @@ -'use server'; -import { getClientWithToken } from '../apollo/client'; -import * as GQL from '../types'; - -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: input, - }); -} - -export async function getCustomer(input: GQL.GetCustomerQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetCustomerDocument, - variables: input, - }); -} - -export async function getCustomerMasters(input: GQL.GetCustomerMastersQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetCustomerMastersDocument, - variables: input, - }); -} - -export async function getCustomerClients(input: GQL.GetCustomerClientsQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetCustomerClientsDocument, - variables: input, - }); -} - -type AddCustomerMasterInput = Pick & { - 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: input, - }); -} diff --git a/packages/graphql/api/customers.ts b/packages/graphql/api/customers.ts new file mode 100644 index 0000000..802c732 --- /dev/null +++ b/packages/graphql/api/customers.ts @@ -0,0 +1,116 @@ +import { getClientWithToken } from '../apollo/client'; +import * as GQL from '../types'; +import { BaseService } from './base'; +import { type VariablesOf } from '@graphql-typed-document-node/core'; + +export class CustomersService extends BaseService { + async addMasters(variables: VariablesOf) { + const newMasterIds = variables.data.masters; + + const { mutate, query } = await getClientWithToken(); + const getMastersResult = await query({ + query: GQL.GetMastersDocument, + variables, + }); + + const existingMasterIds = getMastersResult?.data?.customers + ?.at(0) + ?.masters.map((x) => x?.documentId); + + const newMastersIds = [...new Set([...(existingMasterIds || []), ...(newMasterIds || [])])]; + + const mutationResult = await mutate({ + mutation: GQL.UpdateCustomerDocument, + variables: { + data: { masters: newMastersIds }, + documentId: variables.documentId, + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async createCustomer(variables: VariablesOf) { + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.CreateCustomerDocument, + variables, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async getCustomer(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetCustomerDocument, + variables, + }); + + if (result.error) throw new Error(result.error.message); + + const customer = result.data.customers.at(0); + + return { customer }; + } + + async getClients(variables?: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetClientsDocument, + variables: { + telegramId: variables?.telegramId || this.customer.telegramId, + }, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async getMasters(variables?: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetMastersDocument, + variables: { + telegramId: variables?.telegramId || this.customer.telegramId, + }, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async updateCustomer( + variables: Omit, 'documentId'>, + ) { + const { customer } = await this.getCustomer(this.customer); + + if (!customer) throw new Error('Customer not found'); + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.UpdateCustomerDocument, + variables: { + data: variables.data, + documentId: customer.documentId, + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } +} diff --git a/packages/graphql/api/index.ts b/packages/graphql/api/index.ts deleted file mode 100644 index 4f5a143..0000000 --- a/packages/graphql/api/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './auth'; -export * from './customer'; -export * from './slot'; -export * from './order'; -export * from './service'; diff --git a/packages/graphql/api/order.ts b/packages/graphql/api/order.ts deleted file mode 100644 index 80298f7..0000000 --- a/packages/graphql/api/order.ts +++ /dev/null @@ -1,21 +0,0 @@ -'use server'; -import { getClientWithToken } from '../apollo/client'; -import * as GQL from '../types'; - -export async function getOrder(variables: GQL.GetOrderQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetOrderDocument, - variables, - }); -} - -export async function createOrder(input: GQL.CreateOrderMutationVariables['input']) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.CreateOrderDocument, - variables: { input }, - }); -} diff --git a/packages/graphql/api/orders.ts b/packages/graphql/api/orders.ts new file mode 100644 index 0000000..dfe002b --- /dev/null +++ b/packages/graphql/api/orders.ts @@ -0,0 +1,100 @@ +/* eslint-disable canonical/id-match */ +import { getClientWithToken } from '../apollo/client'; +import * as GQL from '../types'; +import { Enum_Customer_Role, Enum_Slot_State } from '../types'; +import { formatDate, formatTime, sumTime } from '../utils/datetime-format'; +import { BaseService } from './base'; +import { CustomersService } from './customers'; +import { ServicesService } from './services'; +import { SlotsService } from './slots'; +import { type VariablesOf } from '@graphql-typed-document-node/core'; + +const ERRORS = { + INVALID_CLIENT: 'Invalid client', + INVALID_MASTER: 'Invalid master', + MISSING_CLIENT: 'Missing client id', + MISSING_SERVICE_ID: 'Missing service id', + MISSING_SERVICES: 'Missing services', + MISSING_SLOT: 'Missing slot id', + MISSING_START_TIME: 'Missing time start', + SLOT_CLOSED: 'Slot is closed', +}; + +export class OrdersService extends BaseService { + async createOrder(variables: { + input: Omit['input'], 'time_end'>; + }) { + if (!variables.input.slot) throw new Error(ERRORS.MISSING_SLOT); + if (!variables.input.client) throw new Error(ERRORS.MISSING_CLIENT); + if (!variables.input.services?.length) throw new Error(ERRORS.MISSING_SERVICES); + if (!variables.input.services[0]) throw new Error(ERRORS.MISSING_SERVICE_ID); + if (!variables.input.time_start) throw new Error(ERRORS.MISSING_START_TIME); + + const customersService = new CustomersService(this.customer); + const slotsService = new SlotsService(this.customer); + const servicesService = new ServicesService(this.customer); + + const { customer } = await customersService.getCustomer(this.customer); + const { slot } = await slotsService.getSlot({ documentId: variables.input.slot }); + + if (slot?.state === Enum_Slot_State.Closed) { + throw new Error(ERRORS.SLOT_CLOSED); + } + + if (customer?.role === Enum_Customer_Role.Client) { + if (customer.documentId !== variables.input.client) { + throw new Error(ERRORS.INVALID_CLIENT); + } + + const masters = await customersService.getMasters(this.customer); + const masterId = slot?.master?.documentId; + if (!masters.customers.some((master) => master?.documentId === masterId)) { + throw new Error(ERRORS.INVALID_MASTER); + } + } + + if ( + customer?.role === Enum_Customer_Role.Master && + slot?.master?.documentId !== customer.documentId + ) { + throw new Error(ERRORS.INVALID_MASTER); + } + + const { service } = await servicesService.getService({ + documentId: variables.input.services[0], + }); + const endTime = sumTime(variables.input.time_start, service?.duration); + const { mutate } = await getClientWithToken(); + const mutationResult = await mutate({ + mutation: GQL.CreateOrderDocument, + variables: { + input: { + client: variables.input.client, + date: formatDate(variables.input.date).db(), + services: variables.input.services, + slot: variables.input.slot, + time_end: formatTime(endTime).db(), + time_start: formatTime(variables.input.time_start).db(), + }, + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async getOrder(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetOrderDocument, + variables, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } +} diff --git a/packages/graphql/api/service.ts b/packages/graphql/api/service.ts deleted file mode 100644 index fd4b00e..0000000 --- a/packages/graphql/api/service.ts +++ /dev/null @@ -1,21 +0,0 @@ -'use server'; -import { getClientWithToken } from '../apollo/client'; -import * as GQL from '../types'; - -export async function getServices(input: GQL.GetServicesQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetServicesDocument, - variables: input, - }); -} - -export async function getService(input: GQL.GetServiceQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetServiceDocument, - variables: input, - }); -} diff --git a/packages/graphql/api/services.ts b/packages/graphql/api/services.ts new file mode 100644 index 0000000..202d514 --- /dev/null +++ b/packages/graphql/api/services.ts @@ -0,0 +1,32 @@ +import { getClientWithToken } from '../apollo/client'; +import * as GQL from '../types'; +import { BaseService } from './base'; +import { type VariablesOf } from '@graphql-typed-document-node/core'; + +export class ServicesService extends BaseService { + async getService(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetServiceDocument, + variables, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async getServices(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetServicesDocument, + variables, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } +} diff --git a/packages/graphql/api/slot.ts b/packages/graphql/api/slot.ts deleted file mode 100644 index 8e872ad..0000000 --- a/packages/graphql/api/slot.ts +++ /dev/null @@ -1,48 +0,0 @@ -'use server'; -import { getClientWithToken } from '../apollo/client'; -import * as GQL from '../types'; - -export async function createSlot(input: GQL.CreateSlotMutationVariables['input']) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.CreateSlotDocument, - variables: { input }, - }); -} - -export async function getSlots(input: GQL.GetSlotsQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetSlotsDocument, - variables: input, - }); -} - -export async function getSlot(input: GQL.GetSlotQueryVariables) { - const { query } = await getClientWithToken(); - - return query({ - query: GQL.GetSlotDocument, - variables: input, - }); -} - -export async function updateSlot(input: GQL.UpdateSlotMutationVariables) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.UpdateSlotDocument, - variables: input, - }); -} - -export async function deleteSlot(input: GQL.DeleteSlotMutationVariables) { - const { mutate } = await getClientWithToken(); - - return mutate({ - mutation: GQL.DeleteSlotDocument, - variables: input, - }); -} diff --git a/packages/graphql/api/slots.ts b/packages/graphql/api/slots.ts new file mode 100644 index 0000000..6b50c94 --- /dev/null +++ b/packages/graphql/api/slots.ts @@ -0,0 +1,98 @@ +import { getClientWithToken } from '../apollo/client'; +import * as GQL from '../types'; +import { formatDate, formatTime } from '../utils/datetime-format'; +import { BaseService } from './base'; +import { CustomersService } from './customers'; +import { type VariablesOf } from '@graphql-typed-document-node/core'; + +export class SlotsService extends BaseService { + async createSlot(variables: VariablesOf) { + const customerService = new CustomersService(this.customer); + + const { customer } = await customerService.getCustomer(this.customer); + + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.CreateSlotDocument, + variables: { + ...variables, + date: formatDate(variables.input.date).db(), + master: customer?.documentId, + time_end: formatTime(variables.input.time_end).db(), + time_start: formatTime(variables.input.time_start).db(), + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async deleteSlot(variables: VariablesOf) { + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.DeleteSlotDocument, + variables, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } + + async getSlot(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetSlotDocument, + variables, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async getSlots(variables: VariablesOf) { + const { query } = await getClientWithToken(); + + const result = await query({ + query: GQL.GetSlotsDocument, + variables: { + filters: { + ...variables.filters, + date: { eq: formatDate(variables?.filters?.date?.eq).db() }, + }, + }, + }); + + if (result.error) throw new Error(result.error.message); + + return result.data; + } + + async updateSlot(variables: VariablesOf) { + const { mutate } = await getClientWithToken(); + + const mutationResult = await mutate({ + mutation: GQL.UpdateSlotDocument, + variables: { + ...variables, + date: variables.data?.date ? formatDate(variables.data.date).db() : undefined, + time_end: variables.data?.time_end ? formatTime(variables.data.time_end).db() : undefined, + time_start: variables.data?.time_start + ? formatTime(variables.data.time_start).db() + : undefined, + }, + }); + + const error = mutationResult.errors?.at(0); + if (error) throw new Error(error.message); + + return mutationResult.data; + } +} diff --git a/packages/graphql/config/token.ts b/packages/graphql/config/token.ts index deb26c2..e61bfc6 100644 --- a/packages/graphql/config/token.ts +++ b/packages/graphql/config/token.ts @@ -1,4 +1,4 @@ -import { login } from '../api'; +import { login } from '../api/auth'; import { isTokenExpired } from '../utils/jwt'; export const token: null | string = null; diff --git a/packages/graphql/eslint.config.js b/packages/graphql/eslint.config.js index e5e51e2..3fb0644 100644 --- a/packages/graphql/eslint.config.js +++ b/packages/graphql/eslint.config.js @@ -4,6 +4,6 @@ import { typescript } from '@repo/eslint-config/typescript'; export default [ ...typescript, { - ignores: ['**/graphql/types.ts', '**/schema.graphql'], + ignores: ['**/types/**'], }, ]; diff --git a/packages/graphql/graphql.config.cjs b/packages/graphql/graphql.config.cjs index c1d69b9..f575ce9 100644 --- a/packages/graphql/graphql.config.cjs +++ b/packages/graphql/graphql.config.cjs @@ -5,9 +5,12 @@ module.exports = { './types/operations.generated.ts': { config: { avoidOptionals: false, + maybeValue: 'T | null | undefined', onlyOperationTypes: true, + scalars: { + Long: 'number', + }, useTypeImports: true, - maybeValue: 'T | null | undefined' }, plugins: ['typescript', 'typescript-operations', 'typed-document-node'], }, diff --git a/packages/graphql/operations/customer.graphql b/packages/graphql/operations/customer.graphql index b3868cf..d45f566 100644 --- a/packages/graphql/operations/customer.graphql +++ b/packages/graphql/operations/customer.graphql @@ -20,11 +20,15 @@ query GetCustomer($phone: String, $telegramId: Long) { } } -query GetCustomerMasters($phone: String, $telegramId: Long) { +query GetMasters($phone: String, $telegramId: Long, $documentId: ID) { customers( filters: { - or: [{ phone: { eq: $phone } }, { telegramId: { eq: $telegramId } }] - and: [{ active: { eq: true } }] + or: [ + { phone: { eq: $phone } } + { telegramId: { eq: $telegramId } } + { documentId: { eq: $documentId } } + ] + # and: [{ active: { eq: true } }] } ) { documentId @@ -34,11 +38,11 @@ query GetCustomerMasters($phone: String, $telegramId: Long) { } } -query GetCustomerClients($phone: String, $telegramId: Long) { +query GetClients($phone: String, $telegramId: Long) { customers( filters: { or: [{ phone: { eq: $phone } }, { telegramId: { eq: $telegramId } }] - and: [{ active: { eq: true } }] + # and: [{ active: { eq: true } }] } ) { documentId @@ -48,7 +52,7 @@ query GetCustomerClients($phone: String, $telegramId: Long) { } } -mutation UpdateCustomerProfile($documentId: ID!, $data: CustomerInput!) { +mutation UpdateCustomer($documentId: ID!, $data: CustomerInput!) { updateCustomer(documentId: $documentId, data: $data) { ...CustomerFields } diff --git a/packages/graphql/package.json b/packages/graphql/package.json index cef2fac..c44907b 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -9,19 +9,20 @@ }, "dependencies": { "@apollo/client": "catalog:", + "dayjs": "catalog:", "jsonwebtoken": "catalog:", "zod": "catalog:" }, "devDependencies": { - "graphql": "catalog:", - "@repo/eslint-config": "workspace:*", - "@repo/typescript-config": "workspace:*", - "@types/jsonwebtoken": "^9.0.7", "@graphql-codegen/cli": "^5.0.3", "@graphql-codegen/typed-document-node": "^5.0.12", "@graphql-codegen/typescript": "^4.1.2", "@graphql-codegen/typescript-operations": "^4.4.0", "@graphql-typed-document-node/core": "^3.2.0", + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@types/jsonwebtoken": "^9.0.7", + "graphql": "catalog:", "vite-tsconfig-paths": "catalog:", "vitest": "catalog:" } diff --git a/packages/graphql/types/operations.generated.ts b/packages/graphql/types/operations.generated.ts index c0eb19a..15dbf92 100644 --- a/packages/graphql/types/operations.generated.ts +++ b/packages/graphql/types/operations.generated.ts @@ -16,7 +16,7 @@ export type Scalars = { Date: { input: any; output: any; } DateTime: { input: any; output: any; } JSON: { input: any; output: any; } - Long: { input: any; output: any; } + Long: { input: number; output: number; } Time: { input: any; output: any; } }; @@ -651,7 +651,7 @@ export type LoginMutationVariables = Exact<{ export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | 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 CustomerFieldsFragment = { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: number | null | undefined }; export type CreateCustomerMutationVariables = Exact<{ name: Scalars['String']['input']; @@ -668,31 +668,32 @@ export type GetCustomerQueryVariables = Exact<{ }>; -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 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?: number | null | undefined } | null | undefined> }; -export type GetCustomerMastersQueryVariables = Exact<{ +export type GetMastersQueryVariables = Exact<{ + phone?: InputMaybe; + telegramId?: InputMaybe; + documentId?: InputMaybe; +}>; + + +export type GetMastersQuery = { __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?: number | null | undefined } | null | undefined> } | null | undefined> }; + +export type GetClientsQueryVariables = Exact<{ phone?: InputMaybe; telegramId?: InputMaybe; }>; -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 GetClientsQuery = { __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?: number | null | undefined } | null | undefined> } | null | undefined> }; -export type GetCustomerClientsQueryVariables = Exact<{ - phone?: InputMaybe; - telegramId?: InputMaybe; -}>; - - -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<{ +export type UpdateCustomerMutationVariables = Exact<{ documentId: Scalars['ID']['input']; data: CustomerInput; }>; -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 type UpdateCustomerMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', active?: boolean | null | undefined, documentId: string, name: string, phone: string, photoUrl?: string | null | undefined, role: Enum_Customer_Role, telegramId?: number | null | undefined } | null | undefined }; export type OrderFieldsFragment = { __typename?: 'Order', documentId: string, time_start?: any | null | undefined, time_end?: any | null | undefined, state?: Enum_Order_State | null | undefined, order_number?: number | null | undefined, services: Array<{ __typename?: 'Service', documentId: string, name?: string | null | undefined } | null | undefined>, client?: { __typename?: 'Customer', name: string, documentId: string, photoUrl?: string | null | undefined } | null | undefined }; @@ -772,9 +773,9 @@ export const RegisterDocument = {"kind":"Document","definitions":[{"kind":"Opera 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; 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; 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; -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; -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; -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; +export const GetMastersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMasters"},"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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}],"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":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"documentId"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"eq"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}}]}}]}]}}]}}],"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; +export const GetClientsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetClients"},"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":"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; +export const UpdateCustomerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateCustomer"},"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; export const GetOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"order"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"documentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"documentId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"OrderFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OrderFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Order"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"order_number"}},{"kind":"Field","name":{"kind":"Name","value":"services"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}}]}}]}}]} as unknown as DocumentNode; export const CreateOrderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOrder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"OrderInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOrder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"OrderFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"OrderFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Order"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"time_start"}},{"kind":"Field","name":{"kind":"Name","value":"time_end"}},{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"order_number"}},{"kind":"Field","name":{"kind":"Name","value":"services"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"client"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"photoUrl"}}]}}]}}]} as unknown as DocumentNode; export const GetServicesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetServices"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filters"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ServiceFiltersInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"services"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filters"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServiceFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServiceFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Service"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"documentId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"duration"}}]}}]} as unknown as DocumentNode; diff --git a/apps/web/utils/date/index.ts b/packages/graphql/utils/datetime-format.ts similarity index 100% rename from apps/web/utils/date/index.ts rename to packages/graphql/utils/datetime-format.ts diff --git a/packages/typescript-config/base.json b/packages/typescript-config/base.json index 5117f2a..4086abd 100644 --- a/packages/typescript-config/base.json +++ b/packages/typescript-config/base.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { - "declaration": true, - "declarationMap": true, + "declaration": false, + "declarationMap": false, "esModuleInterop": true, "incremental": false, "isolatedModules": true, diff --git a/packages/ui/src/components/ui/avatar.tsx b/packages/ui/src/components/ui/avatar.tsx index ffcf430..5339d21 100644 --- a/packages/ui/src/components/ui/avatar.tsx +++ b/packages/ui/src/components/ui/avatar.tsx @@ -1,4 +1,5 @@ 'use client'; + import * as AvatarPrimitive from '@radix-ui/react-avatar'; import { cn } from '@repo/ui/lib/utils'; import * as React from 'react'; diff --git a/packages/ui/src/components/ui/calendar.tsx b/packages/ui/src/components/ui/calendar.tsx index 8bfb4cd..772cd35 100644 --- a/packages/ui/src/components/ui/calendar.tsx +++ b/packages/ui/src/components/ui/calendar.tsx @@ -2,6 +2,7 @@ /* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable react/no-unstable-nested-components */ 'use client'; + import { buttonVariants } from '@repo/ui/components/ui/button'; import { cn } from '@repo/ui/lib/utils'; import { ru } from 'date-fns/locale'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a73f200..2d3f899 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ catalogs: autoprefixer: specifier: ^10.4.20 version: 10.4.20 + dayjs: + specifier: ^1.11.3 + version: 1.11.13 dotenv-cli: specifier: ^7.4.4 version: 7.4.4 @@ -148,7 +151,7 @@ importers: specifier: ^2.0.19 version: 2.0.19(@types/react@19.1.2)(react@19.1.0) dayjs: - specifier: ^1.11.13 + specifier: 'catalog:' version: 1.11.13 graphql: specifier: 'catalog:' @@ -268,6 +271,9 @@ importers: '@apollo/client': specifier: 'catalog:' version: 3.12.4(@types/react@19.1.2)(graphql-ws@5.16.0(graphql@16.9.0))(graphql@16.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + dayjs: + specifier: 'catalog:' + version: 1.11.13 jsonwebtoken: specifier: 'catalog:' version: 9.0.2 @@ -290,6 +296,9 @@ importers: '@graphql-typed-document-node/core': specifier: ^3.2.0 version: 3.2.0(graphql@16.9.0) + '@repo/eslint-config': + specifier: workspace:* + version: link:../eslint-config '@repo/typescript-config': specifier: workspace:* version: link:../typescript-config diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6c1fe15..853b936 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,7 @@ catalog: "@types/react-dom": ^19.1.2 "@vchikalkin/eslint-config-awesome": ^2.2.2 autoprefixer: ^10.4.20 + dayjs: ^1.11.3 dotenv-cli: ^7.4.4 eslint: ^9.17.0 graphql: ^16.9.0