From 6d47fa7fbb8a3050974c74c145d4988a4e9b33b4 Mon Sep 17 00:00:00 2001 From: smanylov Date: Tue, 2 Dec 2025 17:11:53 +0700 Subject: [PATCH] add tanstack/react-query --- package-lock.json | 27 +++++++++++++++++ package.json | 3 +- src/app/actions/auth.ts | 14 ++++----- src/app/context/data-context.tsx | 43 ---------------------------- src/app/layout.tsx | 5 +++- src/app/lib/action.ts | 27 +++++++++-------- src/app/lib/session.ts | 15 ++++++---- src/app/page.tsx | 2 +- src/app/pages/emptypage/page.tsx | 4 +++ src/app/pages/layout.tsx | 23 +++++++++------ src/app/providers/getQueryClient.ts | 31 ++++++++++++++++++++ src/app/providers/providers.tsx | 36 +++++++++++++++++++++++ src/app/ui/button-test.tsx | 4 +-- src/app/ui/header/userMenuButton.tsx | 39 +++++++++++++++++++------ src/app/ui/test.tsx | 30 +++++++++++++++++++ 15 files changed, 209 insertions(+), 94 deletions(-) delete mode 100644 src/app/context/data-context.tsx create mode 100644 src/app/providers/getQueryClient.ts create mode 100644 src/app/providers/providers.tsx create mode 100644 src/app/ui/test.tsx diff --git a/package-lock.json b/package-lock.json index 6a315b2..2cc6f16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@tailwindcss/postcss": "^4.1.17", + "@tanstack/react-query": "^5.90.11", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "16.0.3", @@ -1884,6 +1885,32 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@tanstack/query-core": { + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz", + "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz", + "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", diff --git a/package.json b/package.json index 6459c6e..62b717c 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,12 @@ "scripts": { "dev": "next dev -p 2999", "build": "next build", - "start": "next start", + "start": "next start -p 2999", "lint": "eslint" }, "dependencies": { "@tailwindcss/postcss": "^4.1.17", + "@tanstack/react-query": "^5.90.11", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "16.0.3", diff --git a/src/app/actions/auth.ts b/src/app/actions/auth.ts index e011703..0713ded 100644 --- a/src/app/actions/auth.ts +++ b/src/app/actions/auth.ts @@ -1,14 +1,14 @@ 'use server' import { SignupFormSchema, loginFormSchema } from '@/app/lib/definitions'; -import { createSession, deleteSession, getSessionToken } from '@/app/lib/session'; +import { createSession, deleteSession, getSessionData } from '@/app/lib/session'; import { redirect } from 'next/navigation' export async function logout() { - const token = await getSessionToken(); + const token = await getSessionData('token'); try { - await fetch('http://localhost:8080/api/auth/logout', { + await fetch('http://localhost/api/auth/logout', { method: 'POST', headers: { "Content-Type": "application/json", @@ -39,7 +39,7 @@ export async function authorization( try { const { email, password } = validatedFields.data; - const response = await fetch('http://localhost:8080/api/auth/login', { + const response = await fetch('http://localhost/api/auth/login', { method: 'POST', body: JSON.stringify({ email: email, @@ -52,7 +52,7 @@ export async function authorization( }); if (response.ok) { let parsed = await response.json(); - await createSession(parsed.token); + await createSession(parsed.token, parsed.email); } else { throw new Error('Something went wrong. from server'); } @@ -96,7 +96,7 @@ export async function registration( try { const { full_name, email, password } = validatedFields.data; - const response = await fetch('http://localhost:8080/api/auth/register', { + const response = await fetch('http://localhost/api/auth/register', { method: 'POST', body: JSON.stringify({ fullName: full_name, email, password }), headers: { @@ -107,7 +107,7 @@ export async function registration( if (response.ok) { let parsed = await response.json(); - await createSession(parsed.token); + await createSession(parsed.token, email); } else { throw new Error('Something went wrong. from server'); } diff --git a/src/app/context/data-context.tsx b/src/app/context/data-context.tsx deleted file mode 100644 index 0310ea5..0000000 --- a/src/app/context/data-context.tsx +++ /dev/null @@ -1,43 +0,0 @@ -'use client' - -import { createContext, useContext, ReactNode } from 'react' - -interface Data { - id: number - name: string - family: string -} - -interface DataContextType { - dataContext: Data | null - isLoading: boolean - error: string | null -} - -const DataContext = createContext(undefined) - -export function DataProvider({ - children, - dataContext -}: { - children: ReactNode - dataContext: any -}) { - return ( - - {children} - - ) -} - -export function useDataContext() { - const context = useContext(DataContext) - if (context === undefined) { - throw new Error('useDataContext must be used within a DataProvider') - } - return context -} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e066a36..cc5af30 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,3 +1,4 @@ +import Providers from './providers/providers' import type { Metadata } from "next"; import "./styles/globals.css"; import "./styles/global-styles.scss" @@ -15,7 +16,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); diff --git a/src/app/lib/action.ts b/src/app/lib/action.ts index 9f8a9a4..c19e224 100644 --- a/src/app/lib/action.ts +++ b/src/app/lib/action.ts @@ -1,4 +1,5 @@ 'use server' +import { getSessionData } from '@/app/lib/session'; const FRUITS_URL = 'https://www.fruityvice.com/api/fruit/'; const ALL = 'all' @@ -25,26 +26,24 @@ export async function fetchFruit(id: number) { } } -export async function testConnect() { +export async function getUserData() { + const userEmail = await getSessionData('email'); + const token = await getSessionData('token'); + try { - const response = await fetch('http://localhost:8080/api/auth/register', { - method: 'POST', - body: JSON.stringify({ - "fullName": "Vladislav Sergevich", - "email": "s2@e.ru", - "password": "1121231412" - }), + const response = await fetch(`http://localhost/api/user?email=${userEmail}`, { + method: 'GET', headers: { "Content-Type": "application/json", - "Accept": "application/json" + "Accept": "application/json", + "Authorization": `Bearer ${token}`, } - }); - let parsed = await response.json(); - - return parsed; + if (response.ok) { + return await response.json(); + } } catch (error) { - throw error; + console.error('error'); } } \ No newline at end of file diff --git a/src/app/lib/session.ts b/src/app/lib/session.ts index 2497f84..c5d510f 100644 --- a/src/app/lib/session.ts +++ b/src/app/lib/session.ts @@ -25,9 +25,13 @@ export async function decrypt(session: string | undefined = '') { } } -export async function createSession(token: string) { +export async function createSession(token: string, email: string) { const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - const session = await encrypt({ token, expiresAt }); + const session = await encrypt({ + token, + email, + expiresAt + }); const cookieStore = await cookies(); cookieStore.set('session', session, { @@ -39,7 +43,7 @@ export async function createSession(token: string) { }) } -export async function getSessionToken(): Promise { +export async function getSessionData(type: 'token' | 'email'): Promise { const cookieStore = await cookies(); const sessionCookie = cookieStore.get('session')?.value; @@ -49,15 +53,14 @@ export async function getSessionToken(): Promise { const payload = await decrypt(sessionCookie); - if (payload && typeof payload === 'object' && 'token' in payload) { - return payload.token as string; + if (payload && typeof payload === 'object' && type in payload) { + return payload[type] as string; } return null; } - export async function deleteSession() { const cookieStore = await cookies() cookieStore.delete('session') diff --git a/src/app/page.tsx b/src/app/page.tsx index a73ec09..14967a7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -11,7 +11,7 @@ export default function Home() {
- Home page11 + Home page
emptypage
+
+ +
); diff --git a/src/app/pages/layout.tsx b/src/app/pages/layout.tsx index 075eba4..2d911f5 100644 --- a/src/app/pages/layout.tsx +++ b/src/app/pages/layout.tsx @@ -1,18 +1,23 @@ import NavLinks from '@/app/ui/nav-links' import styles from '@/app/page.module.scss' import HeaderPanel from '../ui/header/headerPanel'; -import { DataProvider } from '@/app/context/data-context'; -import { fetchFruit } from '@/app/lib/action'; -import { getSessionToken } from '@/app/lib/session'; +import { getQueryClient } from '@/app/providers/getQueryClient'; +import { HydrationBoundary, dehydrate } from '@tanstack/react-query'; +import { getUserData } from '@/app/lib/action'; export default async function Layout({ children }: { children: React.ReactNode }) { + const queryClient = getQueryClient(); - const fruitsData = await fetchFruit(100); - const token = await getSessionToken(); + await Promise.all([ + queryClient.prefetchQuery({ + queryKey: ['userData'], + queryFn: () => getUserData() + }) + ]) return ( - -
+
+
@@ -20,7 +25,7 @@ export default async function Layout({ children }: { children: React.ReactNode } {children}
-
- + +
); } \ No newline at end of file diff --git a/src/app/providers/getQueryClient.ts b/src/app/providers/getQueryClient.ts new file mode 100644 index 0000000..7c45717 --- /dev/null +++ b/src/app/providers/getQueryClient.ts @@ -0,0 +1,31 @@ +import { QueryClient } from '@tanstack/react-query' +import { cache } from 'react' + +const getQueryClientDefaultConfig = () => ({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}) + +export function makeQueryClient() { + return new QueryClient(getQueryClientDefaultConfig()) +} + +export const getQueryClient = cache(() => { + + if (typeof window === 'undefined') { + return new QueryClient(getQueryClientDefaultConfig()) + } else { + // @ts-ignore + if (!globalThis.__APP_QUERY_CLIENT__) { + // @ts-ignore + globalThis.__APP_QUERY_CLIENT__ = makeQueryClient() + } + // @ts-ignore + return globalThis.__APP_QUERY_CLIENT__ + } +}) \ No newline at end of file diff --git a/src/app/providers/providers.tsx b/src/app/providers/providers.tsx new file mode 100644 index 0000000..b049704 --- /dev/null +++ b/src/app/providers/providers.tsx @@ -0,0 +1,36 @@ +'use client' + +import { + isServer, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query' + +export function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + }) +} + +let browserQueryClient: QueryClient | undefined = undefined + +function getQueryClient() { + if (isServer) { + return makeQueryClient() + } else { + if (!browserQueryClient) browserQueryClient = makeQueryClient() + return browserQueryClient + } +} + +export default function Providers({ children }: { children: React.ReactNode }) { + const queryClient = getQueryClient(); + + return ( + {children} + ) +} \ No newline at end of file diff --git a/src/app/ui/button-test.tsx b/src/app/ui/button-test.tsx index 4df5d60..27d549e 100644 --- a/src/app/ui/button-test.tsx +++ b/src/app/ui/button-test.tsx @@ -1,9 +1,7 @@ 'use client' -import { testConnect } from '@/app/lib/action'; - async function clickHandler() { - testConnect(); + console.log('click') } export default function ButtonTest() { diff --git a/src/app/ui/header/userMenuButton.tsx b/src/app/ui/header/userMenuButton.tsx index 2eb63be..0e92588 100644 --- a/src/app/ui/header/userMenuButton.tsx +++ b/src/app/ui/header/userMenuButton.tsx @@ -3,12 +3,23 @@ import { useState, useRef } from 'react'; import { useClickOutside } from '@/app/hooks/useClickOutside'; import Link from 'next/link'; -import { useDataContext } from '@/app/context/data-context'; +import { getUserData } from '@/app/lib/action'; +import { useQuery } from '@tanstack/react-query'; export default function UserMenuButton() { const [isOpen, setIsOpen] = useState(false); const menuRef = useRef(null); - const { dataContext } = useDataContext() + const { + data: userData, + isLoading, + isError, + error + } = useQuery({ + queryKey: ['userData'], + queryFn: () => { + return getUserData(); + } + }) useClickOutside(menuRef, () => { @@ -17,7 +28,6 @@ export default function UserMenuButton() { const handleButtonClick = () => { if (!isOpen) { - console.log(dataContext); setIsOpen(true); } else { setIsOpen(false); @@ -30,18 +40,29 @@ export default function UserMenuButton() { onClick={handleButtonClick} ref={menuRef} > -
- User +
+ {userData?.fullName ? userData.fullName : ''}
-
User
+
+ {userData?.fullName ? userData.fullName : ''} +
-
Валерка
-
va@no-copy.ru
+
+ {userData?.fullName ? userData.fullName : ''} +
+
+ {userData?.email ? userData.email : ''} +
- СТАРТ + {userData?.subscriptionType ? userData.subscriptionType : ''} +
diff --git a/src/app/ui/test.tsx b/src/app/ui/test.tsx new file mode 100644 index 0000000..10f461b --- /dev/null +++ b/src/app/ui/test.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { useQuery } from '@tanstack/react-query'; +import { getUserData } from '@/app/lib/action'; + +export default function HomePage() { + const { + data: userData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ['userData'], + queryFn: () => { + return getUserData(); + }, + }); + + if (isLoading) return
Loading...
; + if (isError) return
Error: {error.message}
; + + return ( +
+

user email

+ + {userData.email} + +
+ ); +} \ No newline at end of file