diff --git a/apps/web/components/contacts/contacts-list.tsx b/apps/web/components/contacts/contacts-list.tsx index 85425d0..5b37b7c 100644 --- a/apps/web/components/contacts/contacts-list.tsx +++ b/apps/web/components/contacts/contacts-list.tsx @@ -15,7 +15,11 @@ export function ContactsList() { return (
{contacts.map((contact) => ( - + service?.name).join(', ')} + key={contact.documentId} + {...contact} + /> ))}
); diff --git a/apps/web/components/orders/order-contacts.tsx b/apps/web/components/orders/order-contacts.tsx index d94a4e0..b90c90a 100644 --- a/apps/web/components/orders/order-contacts.tsx +++ b/apps/web/components/orders/order-contacts.tsx @@ -10,17 +10,19 @@ export function OrderContacts({ documentId }: Readonly) { return (
-

Контакты

+

Участники

{order.slot?.master && ( )} {order.client && ( )} diff --git a/apps/web/components/orders/order-form/contacts-grid.tsx b/apps/web/components/orders/order-form/contacts-grid.tsx index 0bc8498..82a53d8 100644 --- a/apps/web/components/orders/order-form/contacts-grid.tsx +++ b/apps/web/components/orders/order-form/contacts-grid.tsx @@ -1,22 +1,18 @@ -/* eslint-disable canonical/id-match */ 'use client'; import { DataNotFound } from '@/components/shared/alert'; +import { UserAvatar } from '@/components/shared/user-avatar'; import { CardSectionHeader } from '@/components/ui'; import { ContactsContextProvider } from '@/context/contacts'; import { useCustomerContacts } from '@/hooks/api/contacts'; import { useCustomerQuery } from '@/hooks/api/customers'; -// eslint-disable-next-line import/extensions -import AvatarPlaceholder from '@/public/avatar/avatar_placeholder.png'; import { useOrderStore } from '@/stores/order'; import { withContext } from '@/utils/context'; -import { type CustomerFieldsFragment, Enum_Customer_Role } from '@repo/graphql/types'; +import { type CustomerFieldsFragment } from '@repo/graphql/types'; import { Card } from '@repo/ui/components/ui/card'; import { Label } from '@repo/ui/components/ui/label'; import { LoadingSpinner } from '@repo/ui/components/ui/spinner'; import { cn } from '@repo/ui/lib/utils'; -import Image from 'next/image'; -import { useEffect } from 'react'; type ContactsGridProps = { readonly contacts: CustomerFieldsFragment[]; @@ -53,26 +49,11 @@ export function ContactsGridBase({ contacts, onSelect, selected, title }: Contac />
-
- {contact?.name} -
+
store.masterId); const setMasterId = useOrderStore((store) => store.setMasterId); const isLoading = isLoadingContacts || isLoadingCustomer; - useEffect(() => { - setFilter('invitedBy'); - }, [setFilter]); - if (isLoading) return ; - const mastersContacts = (customer ? [{ ...customer, name: 'Я' }] : []).concat( - contacts.filter((contact) => contact.role === Enum_Customer_Role.Master), - ); + const mastersContacts = (customer ? [{ ...customer, name: 'Я' }] : []).concat(contacts); if (!mastersContacts.length) return ; return ( contact.active)} onSelect={(contactId) => setMasterId(contactId)} selected={masterId} title="Выбор мастера" @@ -122,21 +97,17 @@ export const MastersGrid = withContext(ContactsContextProvider)(function () { }); export const ClientsGrid = withContext(ContactsContextProvider)(function () { - const { contacts, isLoading, setFilter } = useCustomerContacts(); + const { contacts, isLoading } = useCustomerContacts(); const clientId = useOrderStore((store) => store.clientId); const setClientId = useOrderStore((store) => store.setClientId); - useEffect(() => { - setFilter('invited'); - }, [setFilter]); - if (isLoading) return ; if (!contacts.length) return ; return ( contact.active)} onSelect={(contactId) => setClientId(contactId)} selected={clientId} title="Выбор клиента" diff --git a/apps/web/components/profile/person-card.tsx b/apps/web/components/profile/person-card.tsx index a7f7a1b..a54053d 100644 --- a/apps/web/components/profile/person-card.tsx +++ b/apps/web/components/profile/person-card.tsx @@ -1,28 +1,27 @@ 'use client'; import { type ProfileProps } from './types'; +import { UserAvatar } from '@/components/shared/user-avatar'; import { useCustomerQuery } from '@/hooks/api/customers'; -import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; import { Card } from '@repo/ui/components/ui/card'; import { LoadingSpinner } from '@repo/ui/components/ui/spinner'; export function PersonCard({ telegramId }: Readonly) { const { data: { customer } = {}, isLoading } = useCustomerQuery({ telegramId }); - if (isLoading || !customer) + if (isLoading) return (
); + if (!customer) return null; + return (
- - - {customer?.name.charAt(0)} - +

{customer?.name}

diff --git a/apps/web/components/profile/subscription-bar.tsx b/apps/web/components/profile/subscription-bar.tsx index 8c069cc..9ca99d0 100644 --- a/apps/web/components/profile/subscription-bar.tsx +++ b/apps/web/components/profile/subscription-bar.tsx @@ -37,7 +37,7 @@ export function SubscriptionInfoBar() { return ( -
+
-
- - - {contact.name.charAt(0)} - +
+

{contact.name}

- {showServices && ( -

- {contact.services.map((service) => service?.name).join(', ')} -

+ {description && ( +

{description}

)}
diff --git a/apps/web/components/shared/order-card.tsx b/apps/web/components/shared/order-card.tsx index 9d8edea..1e9e3bf 100644 --- a/apps/web/components/shared/order-card.tsx +++ b/apps/web/components/shared/order-card.tsx @@ -1,10 +1,10 @@ 'use client'; import { ReadonlyTimeRange } from './time-range/readonly'; +import { UserAvatar } from './user-avatar'; import { getBadge } from '@/components/shared/status'; import { useCustomerQuery } from '@/hooks/api/customers'; import type * as GQL from '@repo/graphql/types'; -import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/ui/avatar'; import { formatDate } from '@repo/utils/datetime-format'; import Link from 'next/link'; @@ -12,8 +12,6 @@ type OrderComponentProps = GQL.OrderFieldsFragment & { showDate?: boolean; }; -type OrderCustomer = GQL.CustomerFieldsFragment; - export function OrderCard({ documentId, showDate, ...order }: Readonly) { const services = order?.services.map((service) => service?.name).join(', '); const { data: { customer } = {} } = useCustomerQuery(); @@ -25,7 +23,7 @@ export function OrderCard({ documentId, showDate, ...order }: Readonly -
+
{order.order_number && ( {order.order_number} @@ -33,7 +31,7 @@ export function OrderCard({ documentId, showDate, ...order }: Readonly
- {avatarSource && } + {avatarSource && }
); } - -function CustomerAvatar({ customer }: { readonly customer: OrderCustomer }) { - if (!customer) return null; - - return ( - - - {customer.name.charAt(0)} - - ); -} diff --git a/apps/web/components/shared/user-avatar.tsx b/apps/web/components/shared/user-avatar.tsx new file mode 100644 index 0000000..761dec5 --- /dev/null +++ b/apps/web/components/shared/user-avatar.tsx @@ -0,0 +1,49 @@ +'use client'; + +import { useCustomerQuery } from '@/hooks/api/customers'; +import { useSubscriptionQuery } from '@/hooks/api/subscriptions'; +// eslint-disable-next-line import/extensions +import AvatarPlaceholder from '@/public/avatar/avatar_placeholder.png'; +import { type CustomerFieldsFragment } from '@repo/graphql/types'; +import { cn } from '@repo/ui/lib/utils'; +import Image from 'next/image'; + +type Sizes = 'lg' | 'md' | 'sm' | 'xl'; + +type UserAvatarProps = Pick & { + readonly className?: string; + readonly size?: Sizes; +}; + +const sizeClasses: Record = { + lg: 'size-28', + md: 'size-20', + sm: 'size-16', + xl: 'size-32', +}; + +export function UserAvatar({ className, size = 'lg', telegramId = null }: UserAvatarProps) { + const { data: { customer } = {}, isLoading } = useCustomerQuery({ telegramId }); + const { data: subscriptionData } = useSubscriptionQuery({ telegramId }); + const hasActiveSubscription = subscriptionData?.hasActiveSubscription; + const sizeClass = sizeClasses[size]; + + return ( +
+
+ {customer?.name +
+
+ ); +} diff --git a/apps/web/hooks/api/customers.ts b/apps/web/hooks/api/customers.ts index c5a0ae0..a4dae8b 100644 --- a/apps/web/hooks/api/customers.ts +++ b/apps/web/hooks/api/customers.ts @@ -7,7 +7,8 @@ import { useSession } from 'next-auth/react'; export const useCustomerQuery = (variables?: Parameters[0]) => { const { data: session } = useSession(); - const telegramId = variables?.telegramId || session?.user?.telegramId; + const telegramId = + variables?.telegramId === undefined ? session?.user?.telegramId : variables?.telegramId; return useQuery({ enabled: Boolean(telegramId), diff --git a/apps/web/hooks/api/subscriptions.ts b/apps/web/hooks/api/subscriptions.ts index 1bfd54d..3b49dc1 100644 --- a/apps/web/hooks/api/subscriptions.ts +++ b/apps/web/hooks/api/subscriptions.ts @@ -17,7 +17,8 @@ import { useSession } from 'next-auth/react'; export const useSubscriptionQuery = (variables?: Parameters[0]) => { const { data: session } = useSession(); - const telegramId = variables?.telegramId || session?.user?.telegramId; + const telegramId = + variables?.telegramId === undefined ? session?.user?.telegramId : variables?.telegramId; return useQuery({ enabled: Boolean(telegramId), diff --git a/packages/graphql/api/subscriptions.ts b/packages/graphql/api/subscriptions.ts index 0084e74..b54d4e1 100644 --- a/packages/graphql/api/subscriptions.ts +++ b/packages/graphql/api/subscriptions.ts @@ -55,7 +55,7 @@ export class SubscriptionsService extends BaseService { // Проверяем, не использовал ли пользователь уже пробный период const { subscription: existingSubscription } = await this.getSubscription({ - telegramId: customer.telegramId, + telegramId: customer?.telegramId, }); if (existingSubscription) { // Проверяем, есть ли в истории успешная пробная подписка @@ -88,7 +88,7 @@ export class SubscriptionsService extends BaseService { const subscriptionData = await this.createSubscription({ input: { autoRenew: false, - customer: customer.documentId, + customer: customer?.documentId, expiresAt: expiresAt.toISOString(), isActive: true, }, @@ -130,9 +130,15 @@ export class SubscriptionsService extends BaseService { const subscription = result.data.subscriptions.at(0); + const hasActiveSubscription = Boolean( + subscription?.isActive && + subscription?.expiresAt && + new Date() < new Date(subscription.expiresAt), + ); + const { maxOrdersPerMonth, remainingOrdersCount } = await this.getRemainingOrdersCount(); - return { maxOrdersPerMonth, remainingOrdersCount, subscription }; + return { hasActiveSubscription, maxOrdersPerMonth, remainingOrdersCount, subscription }; } async getSubscriptionHistory(variables: VariablesOf) {