refactor(contacts): rename masters to invited and update related functionality

- Changed the terminology from "masters" to "invited" and "invitedBy" across the codebase for clarity and consistency.
- Updated the `addContact` function to reflect the new naming convention.
- Refactored API actions and server methods to support the new invited structure.
- Adjusted components and hooks to utilize the updated invited data, enhancing user experience and simplifying logic.
This commit is contained in:
vchikalkin 2025-09-08 13:42:30 +03:00
parent 5dfef524e2
commit 0b188ee5ed
13 changed files with 123 additions and 121 deletions

View File

@ -57,9 +57,9 @@ export async function addContact(conversation: Conversation<Context, Context>, c
if (!documentId) throw new Error('Клиент не создан');
}
// Добавляем текущего мастера к клиенту
const masters = [customer.documentId];
await customerService.addMasters({ data: { masters }, documentId });
// Добавляем текущего пользователя к приглашенному
const invitedBy = [customer.documentId];
await customerService.addInvitedBy({ data: { invitedBy }, documentId });
// Отправляем подтверждения и инструкции
await ctx.reply(await conversation.external(({ t }) => t('msg-contact-added', { name })));

View File

@ -1,8 +1,8 @@
import * as customers from './server/customers';
import { wrapClientAction } from '@/utils/actions';
export const addMasters = wrapClientAction(customers.addMasters);
export const getClients = wrapClientAction(customers.getClients);
export const addInvitedBy = wrapClientAction(customers.addInvitedBy);
export const getInvited = wrapClientAction(customers.getInvited);
export const getCustomer = wrapClientAction(customers.getCustomer);
export const getMasters = wrapClientAction(customers.getMasters);
export const getInvitedBy = wrapClientAction(customers.getInvitedBy);
export const updateCustomer = wrapClientAction(customers.updateCustomer);

View File

@ -6,16 +6,10 @@ import { CustomersService } from '@repo/graphql/api/customers';
const getService = useService(CustomersService);
export async function addMasters(...variables: Parameters<CustomersService['addMasters']>) {
export async function addInvitedBy(...variables: Parameters<CustomersService['addInvitedBy']>) {
const service = await getService();
return wrapServerAction(() => service.addMasters(...variables));
}
export async function getClients(...variables: Parameters<CustomersService['getClients']>) {
const service = await getService();
return wrapServerAction(() => service.getClients(...variables));
return wrapServerAction(() => service.addInvitedBy(...variables));
}
export async function getCustomer(...variables: Parameters<CustomersService['getCustomer']>) {
@ -24,10 +18,16 @@ export async function getCustomer(...variables: Parameters<CustomersService['get
return wrapServerAction(() => service.getCustomer(...variables));
}
export async function getMasters(...variables: Parameters<CustomersService['getMasters']>) {
export async function getInvited(...variables: Parameters<CustomersService['getInvited']>) {
const service = await getService();
return wrapServerAction(() => service.getMasters(...variables));
return wrapServerAction(() => service.getInvited(...variables));
}
export async function getInvitedBy(...variables: Parameters<CustomersService['getInvitedBy']>) {
const service = await getService();
return wrapServerAction(() => service.getInvitedBy(...variables));
}
export async function updateCustomer(...variables: Parameters<CustomersService['updateCustomer']>) {

View File

@ -13,8 +13,8 @@ import { use } from 'react';
const filterLabels: Record<FilterType, string> = {
all: 'Все',
clients: 'Клиенты',
masters: 'Мастера',
invited: 'Приглашенные',
invitedBy: 'Кто пригласил',
};
export function ContactsFilter() {
@ -30,8 +30,8 @@ export function ContactsFilter() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setFilter('all')}>Все</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFilter('clients')}>Клиенты</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFilter('masters')}>Мастера</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFilter('invited')}>Приглашенные</DropdownMenuItem>
<DropdownMenuItem onClick={() => setFilter('invitedBy')}>Кто пригласил</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);

View File

@ -86,13 +86,13 @@ export function ContactsGridBase({ contacts, onSelect, selected, title }: Contac
);
}
export const MastersGrid = withContext(ContactsContextProvider)(function () {
export const InvitedByGrid = withContext(ContactsContextProvider)(function () {
const { contacts, isLoading, setFilter } = useCustomerContacts();
const masterId = useOrderStore((store) => store.masterId);
const setMasterId = useOrderStore((store) => store.setMasterId);
useEffect(() => {
setFilter('masters');
setFilter('invitedBy');
}, [setFilter]);
if (isLoading) return <LoadingSpinner />;
@ -102,18 +102,18 @@ export const MastersGrid = withContext(ContactsContextProvider)(function () {
contacts={contacts}
onSelect={(contactId) => setMasterId(contactId)}
selected={masterId}
title="Мастера"
title="Кто пригласил"
/>
);
});
export const ClientsGrid = withContext(ContactsContextProvider)(function () {
export const InvitedGrid = withContext(ContactsContextProvider)(function () {
const { contacts, isLoading, setFilter } = useCustomerContacts();
const clientId = useOrderStore((store) => store.clientId);
const setClientId = useOrderStore((store) => store.setClientId);
useEffect(() => {
setFilter('clients');
setFilter('invited');
}, [setFilter]);
if (isLoading) return <LoadingSpinner />;
@ -123,7 +123,7 @@ export const ClientsGrid = withContext(ContactsContextProvider)(function () {
contacts={contacts}
onSelect={(contactId) => setClientId(contactId)}
selected={clientId}
title="Клиенты"
title="Приглашенные"
/>
);
});

View File

@ -1,7 +1,7 @@
'use client';
import { BackButton } from './back-button';
import { ClientsGrid, MastersGrid } from './contacts-grid';
import { InvitedGrid, InvitedByGrid } from './contacts-grid';
import { DateTimeSelect } from './datetime-select';
import { NextButton } from './next-button';
import { ErrorPage, SuccessPage } from './result';
@ -15,10 +15,10 @@ import { LoadingSpinner } from '@repo/ui/components/ui/spinner';
import { type JSX } from 'react';
const STEP_COMPONENTS: Record<string, JSX.Element> = {
'client-select': <ClientsGrid />,
'client-select': <InvitedGrid />,
'datetime-select': <DateTimeSelect />,
error: <ErrorPage />,
'master-select': <MastersGrid />,
'master-select': <InvitedByGrid />,
'service-select': <ServicesSelect />,
success: <SuccessPage />,
};

View File

@ -2,7 +2,7 @@
import { createContext, type PropsWithChildren, useMemo, useState } from 'react';
export type FilterType = 'all' | 'clients' | 'masters';
export type FilterType = 'all' | 'invited' | 'invitedBy';
type ContextType = { filter: FilterType; setFilter: (filter: FilterType) => void };

View File

@ -1,23 +1,23 @@
import { getClients, getMasters } from '@/actions/api/customers';
import { getInvited, getInvitedBy } from '@/actions/api/customers';
import { useQuery } from '@tanstack/react-query';
import { useSession } from 'next-auth/react';
export const useClientsQuery = (props?: Parameters<typeof getClients>[0]) => {
export const useInvitedQuery = (props?: Parameters<typeof getInvited>[0]) => {
const { data: session } = useSession();
const telegramId = props?.telegramId || session?.user?.telegramId;
return useQuery({
queryFn: () => getClients({ telegramId }),
queryKey: ['customer', 'telegramId', telegramId, 'clients'],
queryFn: () => getInvited({ telegramId }),
queryKey: ['customer', 'telegramId', telegramId, 'invited'],
});
};
export const useMastersQuery = (props?: Parameters<typeof getMasters>[0]) => {
export const useInvitedByQuery = (props?: Parameters<typeof getInvitedBy>[0]) => {
const { data: session } = useSession();
const telegramId = props?.telegramId || session?.user?.telegramId;
return useQuery({
queryFn: () => getMasters({ telegramId }),
queryKey: ['customer', 'telegramId', telegramId, 'masters'],
queryFn: () => getInvitedBy({ telegramId }),
queryKey: ['customer', 'telegramId', telegramId, 'invitedBy'],
});
};

View File

@ -1,6 +1,6 @@
'use client';
import { useClientsQuery, useMastersQuery } from './query';
import { useInvitedByQuery, useInvitedQuery } from './query';
import { ContactsContext } from '@/context/contacts';
import { sift } from 'radashi';
import { use, useEffect, useMemo } from 'react';
@ -9,39 +9,39 @@ export function useCustomerContacts() {
const { filter, setFilter } = use(ContactsContext);
const {
data: clientsData,
isLoading: isLoadingClients,
refetch: refetchClients,
} = useClientsQuery();
data: invitedData,
isLoading: isLoadingInvited,
refetch: refetchInvited,
} = useInvitedQuery();
const {
data: mastersData,
isLoading: isLoadingMasters,
refetch: refetchMasters,
} = useMastersQuery();
data: invitedByData,
isLoading: isLoadingInvitedBy,
refetch: refetchInvitedBy,
} = useInvitedByQuery();
const clients = clientsData?.clients || [];
const masters = mastersData?.masters || [];
const invited = invitedData?.invited || [];
const invitedBy = invitedByData?.invitedBy || [];
const isLoading = isLoadingClients || isLoadingMasters;
const isLoading = isLoadingInvited || isLoadingInvitedBy;
useEffect(() => {
if (filter === 'clients') {
refetchClients();
} else if (filter === 'masters') {
refetchMasters();
if (filter === 'invited') {
refetchInvited();
} else if (filter === 'invitedBy') {
refetchInvitedBy();
} else {
refetchClients();
refetchMasters();
refetchInvited();
refetchInvitedBy();
}
}, [filter, refetchClients, refetchMasters]);
}, [filter, refetchInvited, refetchInvitedBy]);
const contacts = useMemo(() => {
if (filter === 'clients') return sift(clients);
if (filter === 'masters') return sift(masters);
if (filter === 'invited') return sift(invited);
if (filter === 'invitedBy') return sift(invitedBy);
return [...sift(clients), ...sift(masters)];
}, [clients, masters, filter]);
return [...sift(invited), ...sift(invitedBy)];
}, [invited, invitedBy, filter]);
return { contacts, filter, isLoading, setFilter };
}

View File

@ -5,10 +5,10 @@ import { BaseService } from './base';
import { type VariablesOf } from '@graphql-typed-document-node/core';
export class CustomersService extends BaseService {
async addMasters(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>) {
async addInvitedBy(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>) {
await this.checkIsBanned();
const newMasterIds = variables.data.masters;
const newInvitedByIds = variables.data.invitedBy;
// Проверяем, что пользователь не пытается изменить поле bannedUntil
if (variables.data.bannedUntil !== undefined) {
@ -16,21 +16,23 @@ export class CustomersService extends BaseService {
}
const { mutate, query } = await getClientWithToken();
const getMastersResult = await query({
query: GQL.GetMastersDocument,
const getInvitedByResult = await query({
query: GQL.GetInvitedByDocument,
variables,
});
const existingMasterIds = getMastersResult?.data?.customers
const existingInvitedByIds = getInvitedByResult?.data?.customers
?.at(0)
?.masters.map((x) => x?.documentId);
?.invitedBy.map((x) => x?.documentId);
const newMastersIds = [...new Set([...(existingMasterIds || []), ...(newMasterIds || [])])];
const newInvitedByIdsList = [
...new Set([...(existingInvitedByIds || []), ...(newInvitedByIds || [])]),
];
const mutationResult = await mutate({
mutation: GQL.UpdateCustomerDocument,
variables: {
data: { masters: newMastersIds },
data: { invitedBy: newInvitedByIdsList },
documentId: variables.documentId,
},
});
@ -41,21 +43,6 @@ export class CustomersService extends BaseService {
return mutationResult.data;
}
async getClients(variables?: VariablesOf<typeof GQL.GetClientsDocument>) {
await this.checkIsBanned();
const { query } = await getClientWithToken();
const result = await query({
query: GQL.GetClientsDocument,
variables,
});
const customer = result.data.customers.at(0);
return customer;
}
async getCustomer(variables: VariablesOf<typeof GQL.GetCustomerDocument>) {
await this.checkIsBanned();
@ -71,13 +58,28 @@ export class CustomersService extends BaseService {
return { customer };
}
async getMasters(variables?: VariablesOf<typeof GQL.GetMastersDocument>) {
async getInvited(variables?: VariablesOf<typeof GQL.GetInvitedDocument>) {
await this.checkIsBanned();
const { query } = await getClientWithToken();
const result = await query({
query: GQL.GetMastersDocument,
query: GQL.GetInvitedDocument,
variables,
});
const customer = result.data.customers.at(0);
return customer;
}
async getInvitedBy(variables?: VariablesOf<typeof GQL.GetInvitedByDocument>) {
await this.checkIsBanned();
const { query } = await getClientWithToken();
const result = await query({
query: GQL.GetInvitedByDocument,
variables,
});

View File

@ -121,8 +121,8 @@ describe('OrdersService', () => {
getCustomer: vi.fn().mockResolvedValue({
customer: mockCustomer,
}),
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));
@ -240,8 +240,8 @@ describe('OrdersService', () => {
getCustomer: vi.fn().mockResolvedValue({
customer: masterCustomer,
}),
getMasters: vi.fn().mockResolvedValue({
masters: [masterCustomer],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [masterCustomer],
}),
}));
@ -381,8 +381,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));
@ -432,8 +432,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));
@ -468,8 +468,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));
@ -509,8 +509,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));
@ -555,8 +555,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [inactiveMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [inactiveMaster],
}),
}));
@ -602,8 +602,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [activeCustomerAsMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [activeCustomerAsMaster],
}),
}));
@ -638,8 +638,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [], // клиент не связан с мастером
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [], // клиент не связан с мастером
}),
}));
@ -685,8 +685,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));
@ -726,8 +726,8 @@ describe('OrdersService', () => {
});
mockCustomersService.mockImplementation(() => ({
getCustomer: mockGetCustomer,
getMasters: vi.fn().mockResolvedValue({
masters: [mockMaster],
getInvitedBy: vi.fn().mockResolvedValue({
invitedBy: [mockMaster],
}),
}));

View File

@ -33,7 +33,7 @@ query GetCustomer($phone: String, $telegramId: Long, $documentId: ID) {
}
}
query GetMasters($phone: String, $telegramId: Long, $documentId: ID) {
query GetInvitedBy($phone: String, $telegramId: Long, $documentId: ID) {
customers(
filters: {
or: [
@ -45,13 +45,13 @@ query GetMasters($phone: String, $telegramId: Long, $documentId: ID) {
}
) {
documentId
masters {
invitedBy {
...CustomerFields
}
}
}
query GetClients($phone: String, $telegramId: Long) {
query GetInvited($phone: String, $telegramId: Long) {
customers(
filters: {
or: [{ phone: { eq: $phone } }, { telegramId: { eq: $telegramId } }]
@ -59,7 +59,7 @@ query GetClients($phone: String, $telegramId: Long) {
}
) {
documentId
clients {
invited {
...CustomerFields
}
}

File diff suppressed because one or more lines are too long