diff --git a/apps/web/app/(auth)/telegram/layout.tsx b/apps/web/app/(auth)/telegram/layout.tsx index eed2cc2..e75b962 100644 --- a/apps/web/app/(auth)/telegram/layout.tsx +++ b/apps/web/app/(auth)/telegram/layout.tsx @@ -1,4 +1,4 @@ -import { TelegramProvider } from '@/providers'; +import { TelegramProvider } from '@/providers/telegram'; import { type PropsWithChildren } from 'react'; export default async function Layout({ children }: Readonly) { diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index def6cb2..49adbfd 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,4 +1,5 @@ -import { AuthProvider } from '@/providers'; +import { AuthProvider } from '@/providers/auth'; +import { QueryProvider } from '@/providers/query'; import { ThemeProvider } from '@/providers/theme-provider'; import { I18nProvider } from '@/utils/i18n/provider'; import '@repo/ui/globals.css'; @@ -18,7 +19,9 @@ export default async function RootLayout({ children }: Readonly - {children} + + {children} + diff --git a/apps/web/hooks/contacts/use-customer-contacts.ts b/apps/web/hooks/contacts/use-customer-contacts.ts index afd6886..a48388e 100644 --- a/apps/web/hooks/contacts/use-customer-contacts.ts +++ b/apps/web/hooks/contacts/use-customer-contacts.ts @@ -1,26 +1,26 @@ -/* eslint-disable promise/prefer-await-to-then */ 'use client'; import { getClients, getMasters } from '@/actions/contacts'; import { ContactsFilterContext } from '@/context/contacts-filter'; -import { useBoundStore } from '@/store'; +import { useQuery } from '@tanstack/react-query'; import { sift } from 'radash'; -import { use, useEffect, useMemo } from 'react'; +import { use, useMemo } from 'react'; export function useCustomerContacts() { const { filter } = use(ContactsFilterContext); - const { clients, masters, setClients, setMasters } = useBoundStore(); - useEffect(() => { - if ((filter === 'masters' || filter === 'all') && !masters) - getMasters().then((response) => { - if (response?.masters) setMasters(response.masters); - }); + const { data: clientsData } = useQuery({ + queryFn: getClients, + queryKey: ['contacts', 'clients'], + }); - if ((filter === 'clients' || filter === 'all') && !clients) - getClients().then((response) => { - if (response?.clients) setClients(response.clients); - }); - }, [filter]); + const clients = clientsData?.clients; + + const { data: mastersData } = useQuery({ + queryFn: getMasters, + queryKey: ['contacts', 'masters'], + }); + + const masters = mastersData?.masters; const contacts = useMemo(() => { switch (filter) { diff --git a/apps/web/package.json b/apps/web/package.json index 07f4f7b..54fc735 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@repo/ui": "workspace:*", + "@tanstack/react-query": "^5.64.1", "@telegram-apps/sdk-react": "^2.0.19", "graphql": "catalog:", "lucide-react": "catalog:", @@ -26,8 +27,7 @@ "react": "catalog:", "react-dom": "catalog:", "use-debounce": "^10.0.4", - "zod": "catalog:", - "zustand": "^5.0.3" + "zod": "catalog:" }, "devDependencies": { "@playwright/test": "^1.49.1", diff --git a/apps/web/providers/index.ts b/apps/web/providers/index.ts deleted file mode 100644 index 88e61f2..0000000 --- a/apps/web/providers/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './auth'; -export * from './telegram'; diff --git a/apps/web/providers/query.tsx b/apps/web/providers/query.tsx new file mode 100644 index 0000000..8eb8748 --- /dev/null +++ b/apps/web/providers/query.tsx @@ -0,0 +1,42 @@ +'use client'; + +// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top +import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + // With SSR, we usually want to set some default staleTime + // above 0 to avoid refetching immediately on the client + staleTime: 60 * 1_000, + }, + }, + }); +} + +let browserQueryClient: QueryClient | undefined; + +export function QueryProvider({ children }: { readonly children: React.ReactNode }) { + // NOTE: Avoid useState when initializing the query client if you don't + // have a suspense boundary between this and the code that may + // suspend because React will throw away the client on the initial + // render if it suspends and there is no boundary + const queryClient = getQueryClient(); + + return {children}; +} + +function getQueryClient() { + if (isServer) { + // Server: always make a new query client + return makeQueryClient(); + } else { + // Browser: make a new query client if we don't already have one + // This is very important, so we don't re-make a new client if React + // suspends during the initial render. This may not be needed if we + // have a suspense boundary BELOW the creation of the query client + if (!browserQueryClient) browserQueryClient = makeQueryClient(); + return browserQueryClient; + } +} diff --git a/apps/web/store/contacts.ts b/apps/web/store/contacts.ts deleted file mode 100644 index a1e80a9..0000000 --- a/apps/web/store/contacts.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type * as GQL from '@repo/graphql/types'; -import { type StateCreator } from 'zustand'; - -export type ContactsStore = { - clients: Clients | null; - masters: Masters | null; - setClients: (clients: Clients) => void; - setMasters: (masters: Masters) => void; -}; - -type Clients = NonNullable['clients']; -type Masters = NonNullable['masters']; - -export const createContactsSlice: StateCreator = (set) => { - return { - clients: null, - masters: null, - setClients: (clients) => set({ clients }), - setMasters: (masters) => set({ masters }), - }; -}; diff --git a/apps/web/store/index.ts b/apps/web/store/index.ts deleted file mode 100644 index 972153a..0000000 --- a/apps/web/store/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ContactsStore, createContactsSlice } from './contacts'; -import { create } from 'zustand'; - -type Store = ContactsStore; - -export const useBoundStore = create()((...a) => ({ ...createContactsSlice(...a) })); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e2ef75..5ec34f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -141,6 +141,9 @@ importers: '@repo/ui': specifier: workspace:* version: link:../../packages/ui + '@tanstack/react-query': + specifier: ^5.64.1 + version: 5.64.1(react@19.0.0) '@telegram-apps/sdk-react': specifier: ^2.0.19 version: 2.0.19(@types/react@19.0.1)(react@19.0.0) @@ -177,9 +180,6 @@ importers: zod: specifier: 'catalog:' version: 3.24.1 - zustand: - specifier: ^5.0.3 - version: 5.0.3(@types/react@19.0.1)(react@19.0.0) devDependencies: '@playwright/test': specifier: ^1.49.1 @@ -2440,6 +2440,14 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@tanstack/query-core@5.64.1': + resolution: {integrity: sha512-978Wx4Wl4UJZbmvU/rkaM9cQtXXrbhK0lsz/UZhYIbyKYA8E4LdomTwyh2GHZ4oU0BKKoDH4YlKk2VscCUgNmg==} + + '@tanstack/react-query@5.64.1': + resolution: {integrity: sha512-vW5ggHpIO2Yjj44b4sB+Fd3cdnlMJppXRBJkEHvld6FXh3j5dwWJoQo7mGtKI2RbSFyiyu/PhGAy0+Vv5ev9Eg==} + peerDependencies: + react: ^18 || ^19 + '@telegraf/types@7.1.0': resolution: {integrity: sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==} @@ -6308,24 +6316,6 @@ packages: zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} - zustand@5.0.3: - resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - snapshots: '@alloc/quick-lru@5.2.0': {} @@ -8709,6 +8699,13 @@ snapshots: dependencies: tslib: 2.8.1 + '@tanstack/query-core@5.64.1': {} + + '@tanstack/react-query@5.64.1(react@19.0.0)': + dependencies: + '@tanstack/query-core': 5.64.1 + react: 19.0.0 + '@telegraf/types@7.1.0': {} '@telegram-apps/bridge@1.7.1': @@ -13080,8 +13077,3 @@ snapshots: zen-observable@0.8.15: {} zod@3.24.1: {} - - zustand@5.0.3(@types/react@19.0.1)(react@19.0.0): - optionalDependencies: - '@types/react': 19.0.1 - react: 19.0.0