From 7a3c687159f97e2dff28959e9e666b1a7e460578 Mon Sep 17 00:00:00 2001 From: Assad Isah Date: Sat, 23 Aug 2025 13:17:51 +0100 Subject: [PATCH] feat: implement authentication state management with zustand and integrate react-query for data fetching --- app/layout.tsx | 5 +- app/page.tsx | 106 ++++++++++++++++++++++++++++++----------- app/query-provider.tsx | 12 +++++ package-lock.json | 63 ++++++++++++++++++++++-- package.json | 4 +- stores/auth-store.ts | 30 ++++++++++++ 6 files changed, 187 insertions(+), 33 deletions(-) create mode 100644 app/query-provider.tsx create mode 100644 stores/auth-store.ts diff --git a/app/layout.tsx b/app/layout.tsx index 9ceedf8..59e178d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from 'next' import { Inter } from 'next/font/google' import './globals.css' import { MswProvider } from './msw-provider' +import { QueryProvider } from './query-provider' const inter = Inter({ subsets: ['latin'] }) @@ -18,7 +19,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ) diff --git a/app/page.tsx b/app/page.tsx index 221f09c..a152fbd 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,45 +1,95 @@ 'use client' -import { useState } from 'react' -import type { UserProfile } from '@/lib/api-schema' +import { useQuery } from '@tanstack/react-query' +import type { PotentialMatchesResponse, UserProfile } from '@/lib/api-schema' +import { useAuthStore } from '@/stores/auth-store' -export default function Home() { - const [data, setData] = useState(null) - const [loading, setLoading] = useState(false) +const fetchPotentialMatches = async (): Promise => { + const res = await fetch('/api/users/potential-matches') + if (!res.ok) { + throw new Error('Network response was not ok') + } + const data: PotentialMatchesResponse = await res.json() + return data.users +} + +function UserProfileHeader() { + const { user, isAuthenticated, logout } = useAuthStore() + + if (!isAuthenticated) { + return ( +
+

Please log in to find your matches.

+
+ ) + } + + return ( +
+

+ Welcome, {user?.name}! +

+ +
+ ) +} - const fetchData = async () => { - setLoading(true) +function LoginButton() { + const { login, isAuthenticated } = useAuthStore() + + const handleLogin = async () => { try { - const res = await fetch('/api/users/potential-matches') - const json = await res.json() - setData(json.users) + const res = await fetch('/api/auth/login', { method: 'POST' }) + const data = await res.json() + login(data.user, data.token) } catch (error) { - console.error('Failed to fetch mock data:', error) - } finally { - setLoading(false) + console.error('Login failed:', error) } } + if (isAuthenticated) return null + return ( -
-

Chordially Dev Home

+ + ) +} - +export default function Home() { + const { data, isLoading, isError, error } = useQuery({ + queryKey: ['potentialMatches'], + queryFn: fetchPotentialMatches, + }) + + return ( +
+
+

Chordially

+ +
+ +
+
- {data && ( -
-

Mock Data Received:

-
+      
+

Swipe Deck (Mock Data)

+ {isLoading &&

Finding users...

} + {isError &&

Error: {error.message}

} + {data && ( +
             {JSON.stringify(data, null, 2)}
           
-
- )} + )} +
) } diff --git a/app/query-provider.tsx b/app/query-provider.tsx new file mode 100644 index 0000000..0f97694 --- /dev/null +++ b/app/query-provider.tsx @@ -0,0 +1,12 @@ +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ReactNode, useState } from 'react' + +export function QueryProvider({ children }: { children: ReactNode }) { + const [queryClient] = useState(() => new QueryClient()) + + return ( + {children} + ) +} diff --git a/package-lock.json b/package-lock.json index 197751f..a86ff37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "chordially", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.85.5", "next": "15.5.0", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1436,6 +1438,32 @@ "tailwindcss": "4.1.12" } }, + "node_modules/@tanstack/query-core": { + "version": "5.85.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.85.5.tgz", + "integrity": "sha512-KO0WTob4JEApv69iYp1eGvfMSUkgw//IpMnq+//cORBzXf0smyRwPLrUvEe5qtAEGjwZTXrjxg+oJNP/C00t6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.85.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz", + "integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.85.5" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", @@ -1489,7 +1517,7 @@ "version": "19.1.11", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.11.tgz", "integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2669,7 +2697,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -6825,6 +6853,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "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 + } + } } } } diff --git a/package.json b/package.json index 03df915..cfd30c3 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,11 @@ "prepare": "husky" }, "dependencies": { + "@tanstack/react-query": "^5.85.5", "next": "15.5.0", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/stores/auth-store.ts b/stores/auth-store.ts new file mode 100644 index 0000000..15fae8a --- /dev/null +++ b/stores/auth-store.ts @@ -0,0 +1,30 @@ +import { create } from 'zustand' +import type { UserProfile } from '@/lib/api-schema' + +interface AuthState { + user: UserProfile | null + token: string | null + isAuthenticated: boolean + login: (user: UserProfile, token: string) => void + logout: () => void +} + +export const useAuthStore = create((set) => ({ + user: null, + token: null, + isAuthenticated: false, + + login: (user, token) => + set({ + user, + token, + isAuthenticated: true, + }), + + logout: () => + set({ + user: null, + token: null, + isAuthenticated: false, + }), +}))