From d1018c3c167cbc49a1e938c4113d4bba4be1673f Mon Sep 17 00:00:00 2001 From: Anubhav Date: Fri, 3 Oct 2025 01:34:59 +0530 Subject: [PATCH] feat: integrate TanStack React Query for auth mutations - Set up QueryClientProvider with Providers component - Create centralized mutation keys in lib/query/keys.ts - Implement useRegisterMutation and useLoginMutation hooks - Add comprehensive TypeScript types for auth flows - Configure API client with normalized error handling - Enable React Query DevTools for development - Update root layout to include query provider --- frontend/app/layout.tsx | 5 ++- frontend/app/page.tsx | 2 +- frontend/app/providers.tsx | 41 +++++++++++++++++++++ frontend/lib/api/client.ts | 50 +++++++++++++++++++++++++ frontend/lib/query/keys.ts | 10 +++++ frontend/lib/query/mutations/auth.ts | 39 ++++++++++++++++++++ frontend/lib/query/types.ts | 29 +++++++++++++++ frontend/package-lock.json | 55 ++++++++++++++++++++++++++++ frontend/package.json | 14 ++++--- 9 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 frontend/app/providers.tsx create mode 100644 frontend/lib/api/client.ts create mode 100644 frontend/lib/query/keys.ts create mode 100644 frontend/lib/query/mutations/auth.ts create mode 100644 frontend/lib/query/types.ts diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 9a8919f..54313e7 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import { Providers } from "./providers"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,8 +28,8 @@ export default function RootLayout({ - {children} + {children} ); -} +} \ No newline at end of file diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 6a70565..1cb1785 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -4,4 +4,4 @@ export default function Home() {

HomePage

); -} +} \ No newline at end of file diff --git a/frontend/app/providers.tsx b/frontend/app/providers.tsx new file mode 100644 index 0000000..7c3d1ab --- /dev/null +++ b/frontend/app/providers.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { useState, ReactNode } from 'react'; + +interface ProvidersProps { + children: ReactNode; +} + +/** + * Providers component that wraps the app with React Query context + * Includes dev tools for debugging in development mode + */ +export function Providers({ children }: ProvidersProps) { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + refetchOnWindowFocus: false, + }, + mutations: { + onError: (error) => { + console.error('Mutation error:', error); + }, + }, + }, + }) + ); + + return ( + + {children} + {process.env.NODE_ENV === 'development' && ( + + )} + + ); +} \ No newline at end of file diff --git a/frontend/lib/api/client.ts b/frontend/lib/api/client.ts new file mode 100644 index 0000000..a837fb4 --- /dev/null +++ b/frontend/lib/api/client.ts @@ -0,0 +1,50 @@ +import { RegisterInput, LoginInput, AuthResponse } from '../query/types'; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'; + +/** + * API Client for making HTTP requests + * Handles authentication endpoints with proper error handling + */ +class ApiClient { + private async request( + endpoint: string, + options?: RequestInit + ): Promise { + const url = `${API_BASE_URL}${endpoint}`; + + const response = await fetch(url, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({ + message: 'An error occurred', + statusCode: response.status, + })); + throw error; + } + + return response.json(); + } + + async register(data: RegisterInput): Promise { + return this.request('/auth/register', { + method: 'POST', + body: JSON.stringify(data), + }); + } + + async login(data: LoginInput): Promise { + return this.request('/auth/login', { + method: 'POST', + body: JSON.stringify(data), + }); + } +} + +export const apiClient = new ApiClient(); \ No newline at end of file diff --git a/frontend/lib/query/keys.ts b/frontend/lib/query/keys.ts new file mode 100644 index 0000000..8475a60 --- /dev/null +++ b/frontend/lib/query/keys.ts @@ -0,0 +1,10 @@ +/** + * Centralized query and mutation keys for React Query + * This ensures consistent cache management across the application + */ +export const queryKeys = { + auth: { + register: ['auth', 'register'] as const, + login: ['auth', 'login'] as const, + }, +} as const; \ No newline at end of file diff --git a/frontend/lib/query/mutations/auth.ts b/frontend/lib/query/mutations/auth.ts new file mode 100644 index 0000000..bc5b158 --- /dev/null +++ b/frontend/lib/query/mutations/auth.ts @@ -0,0 +1,39 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query'; +import { apiClient } from '@/lib/api/client'; +import { queryKeys } from '../keys'; +import { + RegisterInput, + LoginInput, + AuthResponse, + ApiError, +} from '../types'; + +/** + * Mutation hook for user registration + * @param options - Optional mutation options for callbacks and config + * @returns Mutation object with mutate, isPending, isError, etc. + */ +export function useRegisterMutation( + options?: UseMutationOptions +) { + return useMutation({ + mutationKey: queryKeys.auth.register, + mutationFn: (data: RegisterInput) => apiClient.register(data), + ...options, + }); +} + +/** + * Mutation hook for user login + * @param options - Optional mutation options for callbacks and config + * @returns Mutation object with mutate, isPending, isError, etc. + */ +export function useLoginMutation( + options?: UseMutationOptions +) { + return useMutation({ + mutationKey: queryKeys.auth.login, + mutationFn: (data: LoginInput) => apiClient.login(data), + ...options, + }); +} \ No newline at end of file diff --git a/frontend/lib/query/types.ts b/frontend/lib/query/types.ts new file mode 100644 index 0000000..8a2cb44 --- /dev/null +++ b/frontend/lib/query/types.ts @@ -0,0 +1,29 @@ +/** + * TypeScript types for authentication mutations + */ + +export interface RegisterInput { + email: string; + password: string; + name: string; +} + +export interface LoginInput { + email: string; + password: string; +} + +export interface AuthResponse { + token: string; + user: { + id: string; + email: string; + name: string; + }; +} + +export interface ApiError { + message: string; + statusCode: number; + errors?: Record; +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5fc1b9c..7470578 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.90.2", + "@tanstack/react-query-devtools": "^5.90.2", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0" @@ -1178,6 +1180,59 @@ "tailwindcss": "4.1.13" } }, + "node_modules/@tanstack/query-core": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz", + "integrity": "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.90.1.tgz", + "integrity": "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.2.tgz", + "integrity": "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.90.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.90.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.90.2.tgz", + "integrity": "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.90.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.90.2", + "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/frontend/package.json b/frontend/package.json index 5c50a63..39ee00f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,19 +9,21 @@ "lint": "eslint" }, "dependencies": { + "@tanstack/react-query": "^5.90.2", + "@tanstack/react-query-devtools": "^5.90.2", + "next": "15.5.4", "react": "19.1.0", - "react-dom": "19.1.0", - "next": "15.5.4" + "react-dom": "19.1.0" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", "eslint-config-next": "15.5.4", - "@eslint/eslintrc": "^3" + "tailwindcss": "^4", + "typescript": "^5" } }