add tanstack/react-query
This commit is contained in:
Generated
+27
@@ -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",
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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<DataContextType | undefined>(undefined)
|
||||
|
||||
export function DataProvider({
|
||||
children,
|
||||
dataContext
|
||||
}: {
|
||||
children: ReactNode
|
||||
dataContext: any
|
||||
}) {
|
||||
return (
|
||||
<DataContext.Provider value={{
|
||||
dataContext,
|
||||
isLoading: false,
|
||||
error: null
|
||||
}}>
|
||||
{children}
|
||||
</DataContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useDataContext() {
|
||||
const context = useContext(DataContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useDataContext must be used within a DataProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -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 (
|
||||
<html lang="ru">
|
||||
<body className={``}>
|
||||
<Providers>
|
||||
{children}
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
+13
-14
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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<string | null> {
|
||||
export async function getSessionData(type: 'token' | 'email'): Promise<string | null> {
|
||||
const cookieStore = await cookies();
|
||||
const sessionCookie = cookieStore.get('session')?.value;
|
||||
|
||||
@@ -49,15 +53,14 @@ export async function getSessionToken(): Promise<string | null> {
|
||||
|
||||
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')
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ export default function Home() {
|
||||
<div className={styles.page}>
|
||||
<main className={styles.main}>
|
||||
<div className={styles.intro}>
|
||||
Home page11
|
||||
Home page
|
||||
</div>
|
||||
<Link key={'dashboard'}
|
||||
href={'/pages/dashboard'}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Metadata } from 'next';
|
||||
import Test from '@/app/ui/test';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'emptypage',
|
||||
@@ -11,6 +12,9 @@ export default function Home() {
|
||||
<div>
|
||||
emptypage
|
||||
</div>
|
||||
<div>
|
||||
<Test />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<DataProvider dataContext={fruitsData}>
|
||||
<div className="flex">
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<NavLinks />
|
||||
<div className={`${styles.mainContainter}`}>
|
||||
<HeaderPanel />
|
||||
@@ -20,7 +25,7 @@ export default async function Layout({ children }: { children: React.ReactNode }
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</HydrationBoundary>
|
||||
</div>
|
||||
</DataProvider>
|
||||
);
|
||||
}
|
||||
@@ -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__
|
||||
}
|
||||
})
|
||||
@@ -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 (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { testConnect } from '@/app/lib/action';
|
||||
|
||||
async function clickHandler() {
|
||||
testConnect();
|
||||
console.log('click')
|
||||
}
|
||||
|
||||
export default function ButtonTest() {
|
||||
|
||||
@@ -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<HTMLDivElement | null>(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}
|
||||
>
|
||||
<div className="user-avatar" id="user-menu-btn" title="Валерка">
|
||||
User
|
||||
<div
|
||||
className="user-avatar"
|
||||
id="user-menu-btn"
|
||||
title={userData?.fullName ? userData.fullName : ''}
|
||||
>
|
||||
{userData?.fullName ? userData.fullName : ''}
|
||||
</div>
|
||||
<div className={`user-dropdown ${isOpen ? 'show' : ''}`} id="user-dropdown">
|
||||
<div className="user-info-header">
|
||||
<div className="user-avatar-large">User</div>
|
||||
<div className="user-avatar-large">
|
||||
{userData?.fullName ? userData.fullName : ''}
|
||||
</div>
|
||||
<div className="user-details">
|
||||
<div className="user-name">Валерка</div>
|
||||
<div className="user-email">va@no-copy.ru</div>
|
||||
<div className="user-name">
|
||||
{userData?.fullName ? userData.fullName : ''}
|
||||
</div>
|
||||
<div className="user-email">
|
||||
{userData?.email ? userData.email : ''}
|
||||
</div>
|
||||
<div className="user-subscription">
|
||||
<span className="subscription-badge">
|
||||
СТАРТ</span>
|
||||
{userData?.subscriptionType ? userData.subscriptionType : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 <div>Loading...</div>;
|
||||
if (isError) return <div>Error: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>user email</h1>
|
||||
<span>
|
||||
{userData.email}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user