# Feature Implementation Examples **Project**: Next.js + FastAPI Template **Version**: 1.0 **Last Updated**: 2025-10-31 --- ## Table of Contents 1. [Example 1: User Management Feature](#example-1-user-management-feature) 2. [Example 2: Session Management Feature](#example-2-session-management-feature) 3. [Example 3: Adding Charts to Admin Dashboard](#example-3-adding-charts-to-admin-dashboard) --- ## Example 1: User Management Feature This example shows how to implement a complete user management feature with list, create, edit, and delete functionality. ### Step 1: Create API Hooks **File**: `src/lib/api/hooks/useUsers.ts` ```typescript import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { UsersService, AdminService } from '@/lib/api/generated'; import type { User, CreateUserDto, UpdateUserDto } from '@/lib/api/generated/models'; import { toast } from 'sonner'; import { parseAPIError } from '@/lib/api/errors'; export interface UserFilters { search?: string; is_active?: boolean; is_superuser?: boolean; page?: number; page_size?: number; } // Query: List users export function useUsers(filters?: UserFilters) { return useQuery({ queryKey: ['users', filters], queryFn: async () => { const response = await AdminService.getUsers({ search: filters?.search, isActive: filters?.is_active, isSuperuser: filters?.is_superuser, page: filters?.page || 1, pageSize: filters?.page_size || 20, }); return response; }, staleTime: 60000, // 1 minute }); } // Query: Single user export function useUser(userId: string | undefined) { return useQuery({ queryKey: ['users', userId], queryFn: () => AdminService.getUser({ userId: userId! }), enabled: !!userId, }); } // Mutation: Create user export function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateUserDto) => AdminService.createUser({ requestBody: data }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); toast.success('User created successfully'); }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to create user'); }, }); } // Mutation: Update user export function useUpdateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) => AdminService.updateUser({ userId: id, requestBody: data }), onSuccess: (_, { id }) => { queryClient.invalidateQueries({ queryKey: ['users', id] }); queryClient.invalidateQueries({ queryKey: ['users'] }); toast.success('User updated successfully'); }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to update user'); }, }); } // Mutation: Delete user export function useDeleteUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (userId: string) => AdminService.deleteUser({ userId }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); toast.success('User deleted successfully'); }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to delete user'); }, }); } // Mutation: Activate/Deactivate user export function useToggleUserActive() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ userId, isActive }: { userId: string; isActive: boolean }) => AdminService[isActive ? 'activateUser' : 'deactivateUser']({ userId }), onSuccess: (_, { userId }) => { queryClient.invalidateQueries({ queryKey: ['users', userId] }); queryClient.invalidateQueries({ queryKey: ['users'] }); toast.success('User status updated'); }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to update user status'); }, }); } // Mutation: Bulk actions export function useBulkUserAction() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ action, userIds }: { action: string; userIds: string[] }) => AdminService.bulkUserAction({ requestBody: { action, user_ids: userIds }, }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); toast.success('Bulk action completed successfully'); }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to perform bulk action'); }, }); } ``` ### Step 2: Create User Table Component **File**: `src/components/admin/UserTable.tsx` ```typescript 'use client'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; import { useUsers, useDeleteUser, useBulkUserAction } from '@/lib/api/hooks/useUsers'; import { DataTable } from '@/components/common/DataTable'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { BulkActionBar } from '@/components/admin/BulkActionBar'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { MoreHorizontal, Pencil, Trash2 } from 'lucide-react'; import type { ColumnDef } from '@tanstack/react-table'; import type { User } from '@/lib/api/generated/models'; export function UserTable() { const router = useRouter(); const [selectedIds, setSelectedIds] = useState([]); const [filters, setFilters] = useState({ search: '', page: 1, }); const { data, isLoading } = useUsers(filters); const deleteUser = useDeleteUser(); const bulkAction = useBulkUserAction(); const columns: ColumnDef[] = [ { id: 'select', header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} /> ), }, { accessorKey: 'first_name', header: 'Name', cell: ({ row }) => (
{row.original.first_name} {row.original.last_name}
{row.original.email}
), }, { accessorKey: 'is_active', header: 'Status', cell: ({ row }) => ( {row.original.is_active ? 'Active' : 'Inactive'} ), }, { accessorKey: 'is_superuser', header: 'Role', cell: ({ row }) => ( {row.original.is_superuser ? 'Admin' : 'User'} ), }, { accessorKey: 'created_at', header: 'Created', cell: ({ row }) => new Date(row.original.created_at).toLocaleDateString(), }, { id: 'actions', cell: ({ row }) => ( router.push(`/admin/users/${row.original.id}`)} > Edit handleDelete(row.original.id)} > Delete ), }, ]; const handleDelete = async (userId: string) => { if (confirm('Are you sure you want to delete this user?')) { deleteUser.mutate(userId); } }; const handleBulkAction = async (action: string) => { bulkAction.mutate({ action, userIds: selectedIds }); setSelectedIds([]); }; return (
{selectedIds.length > 0 && ( setSelectedIds([])} /> )} setFilters({ ...filters, page })} onRowSelectionChange={setSelectedIds} />
); } ``` ### Step 3: Create User Form Component **File**: `src/components/admin/UserForm.tsx` ```typescript 'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useCreateUser, useUpdateUser } from '@/lib/api/hooks/useUsers'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Checkbox } from '@/components/ui/checkbox'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form'; import type { User } from '@/lib/api/generated/models'; const userSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters').optional(), first_name: z.string().min(1, 'First name is required'), last_name: z.string().optional(), phone_number: z.string().optional(), is_active: z.boolean().default(true), is_superuser: z.boolean().default(false), }); type UserFormData = z.infer; interface UserFormProps { user?: User; onSuccess?: () => void; onCancel?: () => void; } export function UserForm({ user, onSuccess, onCancel }: UserFormProps) { const createUser = useCreateUser(); const updateUser = useUpdateUser(); const form = useForm({ resolver: zodResolver(userSchema), defaultValues: user || { email: '', first_name: '', last_name: '', phone_number: '', is_active: true, is_superuser: false, }, }); const onSubmit = async (data: UserFormData) => { try { if (user) { await updateUser.mutateAsync({ id: user.id, data }); } else { await createUser.mutateAsync(data); } onSuccess?.(); } catch (error) { // Error handling is done in hooks } }; return (
( Email )} /> {!user && ( ( Password )} /> )} ( First Name )} /> ( Last Name )} /> ( Active )} /> ( Admin )} />
{onCancel && ( )}
); } ``` ### Step 4: Create User List Page **File**: `src/app/(authenticated)/admin/users/page.tsx` ```typescript import { Metadata } from 'next'; import { Button } from '@/components/ui/button'; import { PageHeader } from '@/components/common/PageHeader'; import { UserTable } from '@/components/admin/UserTable'; import Link from 'next/link'; export const metadata: Metadata = { title: 'User Management', description: 'Manage application users', }; export default function UsersPage() { return (
Create User } />
); } ``` ### Step 5: Create User Detail Page **File**: `src/app/(authenticated)/admin/users/[id]/page.tsx` ```typescript import { Metadata } from 'next'; import { UserForm } from '@/components/admin/UserForm'; import { PageHeader } from '@/components/common/PageHeader'; export const metadata: Metadata = { title: 'Edit User', description: 'Edit user details', }; export default function UserDetailPage({ params }: { params: { id: string } }) { return (
); } ``` ### Step 6: Create New User Page **File**: `src/app/(authenticated)/admin/users/new/page.tsx` ```typescript import { Metadata } from 'next'; import { UserForm } from '@/components/admin/UserForm'; import { PageHeader } from '@/components/common/PageHeader'; export const metadata: Metadata = { title: 'Create User', description: 'Add a new user', }; export default function NewUserPage() { return (
); } ``` ### Testing the Feature **Test the user management flow:** 1. Navigate to `/admin/users` 2. Search for users 3. Click "Create User" and fill the form 4. Edit an existing user 5. Delete a user (with confirmation) 6. Test bulk actions (select multiple users, activate/deactivate) 7. Test pagination --- ## Example 2: Session Management Feature This example shows how to implement per-device session management. ### Step 1: Create Session Hooks **File**: `src/lib/api/hooks/useSessions.ts` ```typescript import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { SessionsService } from '@/lib/api/generated'; import { toast } from 'sonner'; import { parseAPIError } from '@/lib/api/errors'; // Query: List my sessions export function useMySessions() { return useQuery({ queryKey: ['sessions', 'me'], queryFn: () => SessionsService.getMySessions(), refetchInterval: 30000, // Refetch every 30 seconds }); } // Mutation: Revoke session export function useRevokeSession() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (sessionId: string) => SessionsService.revokeSession({ sessionId }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['sessions'] }); toast.success('Session revoked successfully'); }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to revoke session'); }, }); } // Mutation: Logout all devices export function useLogoutAllDevices() { return useMutation({ mutationFn: () => AuthService.logoutAll(), onSuccess: () => { // This will logout the user useAuthStore.getState().clearAuth(); window.location.href = '/login'; }, onError: (error) => { const errors = parseAPIError(error); toast.error(errors[0]?.message || 'Failed to logout all devices'); }, }); } ``` ### Step 2: Create Session Card Component **File**: `src/components/settings/SessionCard.tsx` ```typescript 'use client'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Monitor, Smartphone, Tablet } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import type { UserSession } from '@/lib/api/generated/models'; interface SessionCardProps { session: UserSession; onRevoke: () => void; isRevoking?: boolean; } export function SessionCard({ session, onRevoke, isRevoking }: SessionCardProps) { const getDeviceIcon = () => { const deviceName = session.device_name?.toLowerCase() || ''; if (deviceName.includes('mobile') || deviceName.includes('iphone')) { return ; } if (deviceName.includes('tablet') || deviceName.includes('ipad')) { return ; } return ; }; const getLocation = () => { const parts = []; if (session.location_city) parts.push(session.location_city); if (session.location_country) parts.push(session.location_country); return parts.join(', ') || 'Unknown location'; }; return (
{getDeviceIcon()}

{session.device_name || 'Unknown Device'}

{session.is_current && ( This device )}

{getLocation()}

IP: {session.ip_address || 'Unknown'}

Last used {formatDistanceToNow(new Date(session.last_used_at))} ago

); } ``` ### Step 3: Create Session Management Component **File**: `src/components/settings/SessionManagement.tsx` ```typescript 'use client'; import { useState } from 'react'; import { useMySessions, useRevokeSession, useLogoutAllDevices } from '@/lib/api/hooks/useSessions'; import { SessionCard } from './SessionCard'; import { Button } from '@/components/ui/button'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; import { EmptyState } from '@/components/common/EmptyState'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'; import { Smartphone } from 'lucide-react'; export function SessionManagement() { const [showLogoutAllDialog, setShowLogoutAllDialog] = useState(false); const { data: sessions, isLoading } = useMySessions(); const revokeSession = useRevokeSession(); const logoutAll = useLogoutAllDevices(); const otherSessions = sessions?.filter(s => !s.is_current) || []; if (isLoading) { return ; } return (

Active Sessions

Manage your active sessions across devices

{otherSessions.length > 0 && ( )}
{sessions?.map(session => ( revokeSession.mutate(session.id)} isRevoking={revokeSession.isPending} /> ))}
{sessions?.length === 0 && ( } title="No active sessions" description="You don't have any active sessions" /> )} Logout All Other Devices? This will log you out of all other devices. You'll remain logged in on this device. Cancel logoutAll.mutate()}> Logout All
); } ``` ### Step 4: Create Sessions Page **File**: `src/app/(authenticated)/settings/sessions/page.tsx` ```typescript import { Metadata } from 'next'; import { SessionManagement } from '@/components/settings/SessionManagement'; export const metadata: Metadata = { title: 'Sessions', description: 'Manage your active sessions', }; export default function SessionsPage() { return ; } ``` --- ## Example 3: Adding Charts to Admin Dashboard This example shows how to add charts to the admin dashboard. ### Step 1: Create Chart Components (if not exists) **File**: `src/components/charts/LineChartCard.tsx` ```typescript import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; interface LineChartCardProps { title: string; description?: string; data: any[]; dataKey: string; xAxisKey: string; color?: string; } export function LineChartCard({ title, description, data, dataKey, xAxisKey, color = 'hsl(var(--primary))', }: LineChartCardProps) { return ( {title} {description && {description}} ); } ``` ### Step 2: Create Admin Stats Hook **File**: `src/lib/api/hooks/useAdminStats.ts` ```typescript import { useQuery } from '@tanstack/react-query'; import { apiClient } from '@/lib/api/client'; export interface AdminStats { total_users: number; active_users: number; total_organizations: number; user_growth: Array<{ date: string; count: number }>; org_growth: Array<{ date: string; count: number }>; } export function useAdminStats() { return useQuery({ queryKey: ['admin', 'stats'], queryFn: async () => { const response = await apiClient.get('/admin/stats'); return response.data; }, staleTime: 300000, // 5 minutes }); } ``` ### Step 3: Create Admin Dashboard Page **File**: `src/app/(authenticated)/admin/page.tsx` ```typescript import { Metadata } from 'next'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { LineChartCard } from '@/components/charts/LineChartCard'; import { Users, Building, UserCheck } from 'lucide-react'; export const metadata: Metadata = { title: 'Admin Dashboard', description: 'Admin statistics and overview', }; // Note: This would be a client component in real implementation // to use useAdminStats hook export default function AdminDashboardPage() { // const { data: stats, isLoading } = useAdminStats(); return (

Dashboard

{/* Stats Cards */}
Total Users
1,234

+20% from last month

Active Users
1,180

95.6% of total

Organizations
45

+5 this month

{/* Charts */}
); } ``` --- ## Conclusion These examples demonstrate: 1. **Complete CRUD operations** (User Management) 2. **Real-time data with polling** (Session Management) 3. **Data visualization** (Admin Dashboard Charts) Each example follows the established patterns: - API hooks for data fetching - Reusable components - Proper error handling - Loading states - Type safety with TypeScript For more patterns and best practices, refer to: - **ARCHITECTURE.md**: System design - **CODING_STANDARDS.md**: Code style - **COMPONENT_GUIDE.md**: Component usage - **API_INTEGRATION.md**: API patterns