diff --git a/APPLAYOUT_IMPLEMENTATION.md b/APPLAYOUT_IMPLEMENTATION.md deleted file mode 100644 index 562ed56..0000000 --- a/APPLAYOUT_IMPLEMENTATION.md +++ /dev/null @@ -1,339 +0,0 @@ -# Global Layout & Responsive Sidebar - Implementation Summary - -## Overview - -Implemented a global application shell (`AppLayout`) with a responsive sidebar system that adapts to both desktop and mobile viewports. The implementation follows the strict Component → Hook → Service layered architecture pattern. - -## Implementation Details - -### Architecture: Component → Hook → Service Pattern ✓ - -#### 1. **Service Layer** (`services/layoutService.ts`) - -- **Responsibility**: Manages all layout-related API communication and configuration -- **Key Features**: - - Fetches layout configuration from backend API - - Retrieves navigation items based on user context - - Manages user layout preferences (sidebar collapsed state, theme) - - Provides fallback default configuration when API fails - - Type-safe interfaces for all data structures -- **Key Methods**: - - `getLayoutConfig()`: Fetches branding and theme config - - `getNavigationItems()`: Retrieves personalized nav items - - `getUserLayoutPreferences()`: Gets user-specific preferences - - `saveUserLayoutPreferences()`: Persists user preferences - -#### 2. **Custom Hook** (`hooks/useAppLayout.ts`) - -- **Responsibility**: Manages React component state and lifecycle -- **Key Features**: - - Detects mobile vs desktop (breakpoint: lg = 1024px) - - Manages sidebar visibility state - - Manages sidebar collapse state (desktop) - - Handles window resize events - - Provides method to filter nav items by role - - Automatically initializes on mount -- **Exposed API**: - - `state`: Layout state (isLoading, error, isMobile, sidebarOpen, etc.) - - `toggleSidebar()`: Toggle sidebar visibility on mobile - - `closeSidebar()`: Close sidebar - - `openSidebar()`: Open sidebar - - `toggleSidebarCollapse()`: Toggle collapse state on desktop - - `getVisibleNavItems()`: Get filtered navigation items - -#### 3. **UI Component** (`components/layout/AppLayout.tsx`) - -- **Responsibility**: Renders the application shell with responsive behavior -- **Structure**: - - `DesktopSidebar`: Fixed left sidebar (hidden on mobile) - - `MobileBottomSheet`: Bottom-sheet menu (mobile only) - - `NavLink`: Reusable navigation link component - - Main layout wrapper with responsive margins -- **Key Features**: - - Responsive breakpoint: lg (1024px) - - Mobile first approach with progressive enhancement - - Dark mode support throughout - - Loading state with spinner - - Smooth transitions and animations - - Accessibility features (ARIA labels, semantic HTML) - -### Files Created - -| File | Purpose | Lines | -| --------------------------------- | ------------------------ | ----- | -| `services/layoutService.ts` | API integration & config | 186 | -| `hooks/useAppLayout.ts` | State management & logic | 142 | -| `components/layout/AppLayout.tsx` | UI rendering | 268 | - -## Responsive Design - -### Desktop (1024px+) - -- **Sidebar**: Fixed left sidebar, 256px wide (or 80px when collapsed) -- **Layout**: Main content shifts right based on sidebar state -- **Interaction**: Click toggle button to collapse/expand sidebar -- **Features**: - - Collapsible sidebar with smooth transition - - Full navigation items visible - - Descriptions shown under nav labels - -### Mobile (< 1024px, tested at 375px) - -- **Header**: Sticky top header with hamburger menu icon -- **Navigation**: Bottom-sheet drawer (slides up from bottom) -- **Layout**: Full width main content -- **Interactions**: - - Tap hamburger to open bottom-sheet - - Tap X or backdrop to close - - Auto-closes when navigating to a link -- **Features**: - - Non-blocking bottom-sheet (draggable area at top) - - Smooth slide-in animation - - Dark overlay backdrop - - Full navigation items visible in sheet - -## Component Usage - -```tsx -import AppLayout from '@/components/layout/AppLayout'; - -export default function RootLayout({ children }) { - return ( - - - {children} - - - ); -} -``` - -## Data Flow - -``` -Backend API - ↓ -layoutService (fetch & format) - ↓ -useAppLayout Hook (state management) - ↓ -AppLayout Component (render) - ↓ -NavLink Components (interactive) -``` - -## API Integration - -### Expected Backend Endpoints - -1. **GET `/api/layout/config`** - - ```json - { - "success": true, - "data": { - "branding": { - "appName": "SwiftChain", - "logo": "/logo.svg", - "logoDark": "/logo-dark.svg" - }, - "navigation": [...], - "theme": { - "primaryColor": "#3b82f6", - "sidebarCollapsible": true - } - } - } - ``` - -2. **GET `/api/layout/navigation`** - - ```json - { - "success": true, - "data": [ - { - "id": "dashboard", - "label": "Dashboard", - "href": "/dashboard", - "icon": "📊", - "description": "Overview and stats", - "roles": ["customer", "driver", "admin"] - }, - ... - ] - } - ``` - -3. **GET `/api/layout/preferences`** - - ```json - { - "success": true, - "data": { - "sidebarCollapsed": false, - "theme": "light", - "sidebarWidth": 256 - } - } - ``` - -4. **PATCH `/api/layout/preferences`** - - Request: `{ "sidebarCollapsed": boolean, "theme": "light" | "dark" }` - - Response: Same as GET - -### Fallback Strategy - -- If API calls fail, service returns default configuration -- Navigation items still functional with default items -- User preferences default to: sidebar expanded, light theme, 256px width -- No breaking UI changes due to API failures - -## Responsive Breakpoints - -| Breakpoint | Width | Sidebar | -| -------------- | -------------- | ------------- | -| Mobile (xs-sm) | < 1024px | Bottom-sheet | -| Tablet (md) | 768px - 1023px | Bottom-sheet | -| Desktop (lg) | ≥ 1024px | Fixed sidebar | - -## Styling & Theme - -### Color Scheme - -- **Primary**: Uses Tailwind `primary` color (#3b82f6) -- **Light Mode**: Slate-50 background, white sidebar -- **Dark Mode**: Slate-950 background, slate-900 sidebar -- **Hover States**: Slate-100/800 backgrounds -- **Borders**: Slate-200/800 colors - -### Animations - -- **Sidebar Toggle**: 300ms ease transitions -- **Bottom Sheet**: 300ms ease translate animations -- **Nav Items**: 200ms ease color/background transitions -- **Loading Spinner**: Continuous rotation animation - -### Accessibility - -- Semantic HTML (header, nav, main, aside) -- ARIA labels on all interactive elements -- Proper heading hierarchy -- Keyboard navigation support -- Color contrast compliance (WCAG AA) -- Focus management - -## Key Features - -✅ **Responsive Design** - -- Tested at 375px (mobile) and 1024px (desktop) -- Smooth transitions between breakpoints -- Touch-friendly mobile interactions - -✅ **Dark Mode Support** - -- Full dark mode styling throughout -- Automatic theme switching via Tailwind - -✅ **Accessibility** - -- ARIA labels and semantic HTML -- Keyboard navigation -- Proper focus states - -✅ **Performance** - -- Lazy loads navigation data -- Efficient state management -- Minimal re-renders - -✅ **Error Handling** - -- Graceful fallback to default config -- Error state display -- Network error recovery - -✅ **Backend Integration** - -- Real API data source (no mocks) -- User preference persistence -- Role-based navigation (ready for filtering) - -## Testing Recommendations - -### Mobile Testing (375px) - -1. ✓ Hamburger menu appears at top -2. ✓ Tap hamburger opens bottom-sheet -3. ✓ Bottom-sheet animates from bottom -4. ✓ Navigation items visible and tappable -5. ✓ Backdrop click closes bottom-sheet -6. ✓ X button closes bottom-sheet -7. ✓ Automatic close on navigation -8. ✓ Responsive text sizing - -### Desktop Testing (1024px+) - -1. ✓ Fixed sidebar appears on left (256px) -2. ✓ Main content shifts right (margin-left: 256px) -3. ✓ Collapse button works -4. ✓ Sidebar collapses to 80px -5. ✓ Navigation items remain visible when collapsed -6. ✓ Hover states work on nav items -7. ✓ Active route highlighted correctly -8. ✓ Smooth transitions on all state changes - -### API Integration Testing - -1. ✓ Verify API endpoints return proper format -2. ✓ Test fallback when API is down -3. ✓ Test preferences persistence -4. ✓ Test user role-based filtering -5. ✓ Test network error recovery - -### Cross-browser Testing - -- Chrome/Edge (Chromium) -- Firefox -- Safari -- Mobile browsers (Chrome, Safari) - -## Future Enhancements - -- Add nested navigation support -- Implement breadcrumb integration -- Add keyboard shortcuts -- Persistent sidebar state per device -- Animation preferences (respects prefers-reduced-motion) -- Multi-language support -- Custom icon support (SVG components) -- Search/filter navigation items - -## Code Quality - -✅ TypeScript strict mode compliant -✅ ESLint formatted -✅ Prettier compliant -✅ No console warnings -✅ Comprehensive JSDoc comments -✅ Proper error handling and guards -✅ Follows project conventions -✅ Mobile-first responsive approach - -## Related Issues - -Closes #[issue_id] - ---- - -## PR Submission Checklist - -- [ ] All tests pass (mobile + desktop) -- [ ] API endpoints verified -- [ ] Screenshots included (mobile & desktop) -- [ ] Documentation updated -- [ ] No console errors or warnings -- [ ] Dark mode tested -- [ ] Accessibility verified -- [ ] PR description includes this summary diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 4a656c9..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,98 +0,0 @@ -````markdown -# Contributing to SwiftChain Frontend - -Thank you for your interest in contributing to the **SwiftChain Frontend**! We welcome contributions from the community to help build the user interface for our Blockchain-Powered Logistics & Escrow Delivery Platform. - -Whether you are building out new Next.js UI components, fixing bugs, or proposing new user features, your help is incredibly valuable. - -## Product Reference - -Before contributing, please review our comprehensive Product Requirements Document to understand our users, features, and rollout phases: -📄 **[SwiftChain Frontend PRD](https://docs.google.com/document/d/1ShtWjf6i5D5SueeEZ8aH_A3VbnckpbEfPAue4hKwXJo/edit?usp=sharing)** - -## Getting Started - -1. **Fork the repository** on GitHub. -2. **Clone your fork** locally: - ```bash - git clone [https://github.com/your-username/SwiftChain_Frontend.git](https://github.com/your-username/SwiftChain_Frontend.git) - cd SwiftChain_Frontend - ``` -3. **Create a branch** for your feature or bug fix: - ```bash - git checkout -b feature/my-new-feature - ``` - -## Development Workflow - -The frontend application is the customer and administrator-facing interface allowing participants to interact with the logistics network. - -- **Tech Stack**: Next.js App Router, TypeScript, TailwindCSS, React Hook Form, Zod, Axios, TanStack Query, Zustand, WebSocket client. -- **Roles Supported**: Customer, Driver, and Admin. -- **Prerequisites**: Node.js (v18 or higher) and your preferred package manager (npm, yarn, or pnpm). - -### Installation & Setup - -1. **Install dependencies:** - ```bash - pnpm install - ``` -```` - -2. **Setup environment variables:** - - ```bash - cp .env.example .env.local - ``` - - _(Fill in the required local variables inside `.env.local`)_ - -3. **Start the development server:** - ```bash - pnpm dev - ``` - The app will be available at `http://localhost:3000`. - -## Testing & UI Guidelines - -1. **Component Reliability**: Ensure any UI components handle API failures gracefully, display correct loading skeletons, and match the target Acceptance Criteria defined in the PRD (e.g., empty states, toast notifications). -2. **Responsive Design**: All new features must be fully responsive. Test your implementations on both mobile (375px) and desktop viewports. -3. **Accessibility**: Ensure your components are accessible, maintain good color contrast, and include proper ARIA labels. - -## Feature Requests & Git Issues - -We believe the community should drive the project's priorities based on the phased rollout outlined in our PRD (Phase 1 MVP ➔ Escrow ➔ Scaling). - -### Tackling Existing Issues - -When looking for something to work on, please check the GitHub Issues tab. We recommend filtering by our standard priority tags (`high`, `medium`, `low`) or type tags (`frontend`, `ui`). - -### Requesting a New Feature - -1. **Check existing requests**: Browse existing Issues to avoid duplicates. -2. **Open an Issue**: Include a descriptive title, the problem/use case in the logistics flow, your proposed solution, and link back to the PRD document if applicable. - -## Submitting a Pull Request - -1. **Ensure all checks pass**: Run frontend linters and ensure there are zero strict TypeScript errors before submitting. -2. **Update documentation**: If you change the routing architecture or core component structure, update the relevant markdown files. -3. **Format your code**: Run your Prettier/ESLint formatting scripts. -4. **Submit your PR** to the `main` branch. - - Provide a clear description of the visual and functional changes. - - Upload a **screenshot** or video recording of the working UI. - - Include `Closes #[issue_id]` in the description to automatically link the PR to the issue. - ---- - -## License & Copyright - -By contributing, you agree that your contributions will be licensed under the **MIT License**, same as the project. - -++++++++++++++++++++++++++++++++++ -© Copyright -© 2026 SwiftChainn. All rights reserved. -++++++++++++++++++++++++++++++++++ - -``` - -``` diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 6815ccc..0000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,386 +0,0 @@ -# Login Interface Implementation - Summary - -## Overview - -Successfully implemented a complete login interface for SwiftChain Frontend following the Component → Hook → Service architecture pattern as specified in the requirements. - -## Acceptance Criteria - ALL MET ✅ - -### ✅ Form Validation - -- Email field validation (required, valid format) -- Password field validation (required) -- Real-time error clearing on input change -- Client-side validation before submission - -### ✅ Required Features - -- Email/Password input fields with validation -- "Remember Me" checkbox -- "Forgot Password" link -- Password visibility toggle -- Submit button with loading state -- Link to registration page - -### ✅ Strict Layered Architecture - -Implementation follows **Component → Hook → Service** pattern: - -``` -LoginForm (Component) - ↓ -useLogin (Custom Hook) - ↓ -authService (Service Layer) - ↓ -Axios API Calls -``` - -### ✅ Backend API Integration - -- No inline mock objects -- All data retrieved from backend API via `authService` -- Response-based error handling -- Token management and storage - -### ✅ Unit Tests - -- ✅ 54/54 tests passing -- ✅ All test suites pass (9/9) -- ✅ Comprehensive coverage of components, hooks, and services - ---- - -## Implementation Details - -### Files Created - -#### 1. **LoginForm Component** - -[components/forms/LoginForm.tsx](../components/forms/LoginForm.tsx) - -- Renders login UI with all required fields -- Integrates useLogin hook -- Displays validation errors -- Shows/hides password with toggle button -- Remember me checkbox -- Responsive design with TailwindCSS - -#### 2. **useLogin Hook** - -[hooks/useLogin.ts](../hooks/useLogin.ts) - -- Manages form state (values, errors) -- Email format validation with regex -- Real-time error clearing -- Form submission with async API calls -- Token storage in localStorage -- Automatic redirect to dashboard on success -- Error handling and display - -#### 3. **Authentication Service** - -[services/authService.ts](../services/authService.ts) - -- Centralized API communication -- `login()` - Main authentication endpoint -- `logout()` - Clears session -- `requestPasswordReset()` - Forgot password flow -- `resetPassword()` - Password reset with token -- `getCurrentUser()` - Fetch authenticated user -- Proper error handling - -#### 4. **Login Page** - -[app/(auth)/login/page.tsx](<../app/(auth)/login/page.tsx>) - -- Route: `/login` -- Server component wrapper -- Centered layout with responsive styling - -#### 5. **Comprehensive Tests** - -- [hooks/**tests**/useLogin.test.ts](../hooks/__tests__/useLogin.test.ts) - 19 tests -- [components/forms/**tests**/LoginForm.test.tsx](../components/forms/__tests__/LoginForm.test.tsx) - 12 tests -- [services/**tests**/authService.test.ts](../services/__tests__/authService.test.ts) - 10 tests - ---- - -## API Endpoints Used - -```typescript -POST /api/auth/login -{ - email: string, - password: string -} - -// Response -{ - success: boolean, - message: string, - data: { - token: string, - user: { - id: string, - email: string, - name: string, - role: 'customer' | 'driver' | 'admin' - } - } -} -``` - ---- - -## Key Features - -### 1. **Email Validation** - -- Required field validation -- Format validation: `user@domain.com` -- Real-time validation on blur -- Clear error messages - -### 2. **Password Management** - -- Required field validation -- Show/hide password toggle -- Secure input type - -### 3. **User Experience** - -- Loading state on submit button -- Error messages display -- Remember me checkbox -- Forgot password link -- Link to registration -- Responsive mobile design - -### 4. **Security** - -- Token stored in localStorage -- Client-side validation before API call -- Proper error handling (no credential exposure) -- Secure password input - ---- - -## Testing Summary - -### Test Results - -``` -Test Suites: 9 passed, 9 total -Tests: 54 passed, 54 total -Snapshots: 0 total -Time: 6.65 s -``` - -### Test Coverage - -#### useLogin Hook Tests (19 tests) - -- ✅ Initial state validation -- ✅ Email/password value updates -- ✅ Email validation (empty, invalid format, valid) -- ✅ Password validation -- ✅ Error clearing on input -- ✅ Form submission validation -- ✅ API integration -- ✅ Error handling - -#### LoginForm Component Tests (12 tests) - -- ✅ Render all form fields -- ✅ Sign in button rendering -- ✅ Registration link -- ✅ Password visibility toggle -- ✅ Remember me checkbox -- ✅ Input field interactions -- ✅ Form blur handling -- ✅ Form submit handling - -#### authService Tests (10 tests) - -- ✅ Successful login -- ✅ Error handling -- ✅ API endpoint verification -- ✅ Logout functionality -- ✅ Password reset request -- ✅ Password reset confirmation -- ✅ Get current user - ---- - -## Type Safety - -- ✅ Full TypeScript support -- ✅ Interfaces defined for API responses -- ✅ Type-safe hook returns -- ✅ No any types in implementation - ---- - -## Code Quality - -- ✅ ESLint compliant -- ✅ Follows project conventions -- ✅ Proper error handling -- ✅ Clean, readable code -- ✅ Comprehensive comments - ---- - -## Browser Support - -- ✅ Chrome/Edge (latest) -- ✅ Firefox (latest) -- ✅ Safari (latest) -- ✅ Mobile browsers -- ✅ Responsive: 375px - 2560px - ---- - -## Performance - -- ✅ No unnecessary re-renders -- ✅ useCallback for handlers -- ✅ Efficient state management -- ✅ Minimal bundle impact - ---- - -## Compliance with Contributing.md - -✅ Screenshot capability (visual components ready for PR screenshots) -✅ Component reliability (graceful error handling) -✅ Responsive design (mobile and desktop tested) -✅ Accessibility (ARIA labels, keyboard navigation) -✅ Git branch ready: `feat/login-ui` -✅ All unit tests passing -✅ Clear PR description template ready - ---- - -## Next Steps for PR Submission - -1. Create branch: `git checkout -b feat/login-ui` -2. Take screenshots of: - - Login form rendering - - Email validation error - - Password validation error - - Successful login flow - - Test coverage output -3. Commit changes: `git add .` -4. Create pull request with: - - Title: "feat: Implement login interface for customers and drivers" - - Description: - - ``` - Closes #[issue_id] - - ## Summary of Work - Implemented complete login interface with: - - Email/password fields with real-time validation - - Remember me checkbox - - Forgot password link - - Password visibility toggle - - Strict Component → Hook → Service architecture - - Backend API integration (no mock data) - - 54 passing unit tests - - Full TypeScript support - - ## Screenshots - [Include screenshots here] - ``` - - - Include test coverage screenshot - - Link issue with "Closes #[issue_id]" - ---- - -## Files Modified/Created - -### Created - -- ✅ `components/forms/LoginForm.tsx` -- ✅ `hooks/useLogin.ts` -- ✅ `services/authService.ts` -- ✅ `hooks/__tests__/useLogin.test.ts` -- ✅ `components/forms/__tests__/LoginForm.test.tsx` -- ✅ `services/__tests__/authService.test.ts` - -### Modified - -- ✅ `app/(auth)/login/page.tsx` - Updated with LoginForm integration - ---- - -## Verification Commands - -```bash -# Run all tests -pnpm test - -# Type checking (login implementation has no errors) -pnpm type-check - -# Development server -pnpm dev -# Navigate to http://localhost:3000/login -``` - ---- - -## Environment Variables Required - -```env -NEXT_PUBLIC_API_URL= -``` - -Example: `NEXT_PUBLIC_API_URL=http://localhost:3001` - ---- - -## Architecture Diagram - -``` -┌─────────────────────────────────────────────────┐ -│ LoginForm Component │ -│ - Renders UI │ -│ - Calls useLogin hook │ -│ - Displays validation errors │ -└──────────────────┬──────────────────────────────┘ - │ uses - ▼ -┌─────────────────────────────────────────────────┐ -│ useLogin Hook │ -│ - State management (values, errors) │ -│ - Form validation logic │ -│ - Calls authService.login() │ -│ - Handles token storage │ -└──────────────────┬──────────────────────────────┘ - │ calls - ▼ -┌─────────────────────────────────────────────────┐ -│ authService (Service Layer) │ -│ - API communication with Axios │ -│ - POST /api/auth/login │ -│ - Returns token and user data │ -└──────────────────┬──────────────────────────────┘ - │ calls - ▼ -┌─────────────────────────────────────────────────┐ -│ Backend API Endpoint │ -│ - Validates credentials │ -│ - Generates JWT token │ -│ - Returns user profile │ -└─────────────────────────────────────────────────┘ -``` - ---- - -**Implementation Status:** ✅ COMPLETE - Ready for PR submission - -All acceptance criteria have been met. The implementation follows best practices, includes comprehensive testing, and maintains strict architecture patterns as specified in the requirements. diff --git a/MODAL_IMPLEMENTATION.md b/MODAL_IMPLEMENTATION.md deleted file mode 100644 index c93bf2c..0000000 --- a/MODAL_IMPLEMENTATION.md +++ /dev/null @@ -1,444 +0,0 @@ -# Global Modal Framework - Implementation Summary - -## Overview - -Implemented a reusable, accessible modal component framework with focus trapping, backdrop blur, and smooth animations. The system follows strict layered architecture (Component → Hook → Service) with backend API integration for modal templates. - -## Implementation Details - -### Architecture: Component → Hook → Service Pattern ✓ - -#### 1. **Service Layer** (`services/modalService.ts`) - -- **Responsibility**: Manages modal state, lifecycle, and API communication -- **Key Features**: - - Singleton service for centralized modal management - - Event subscription pattern for listeners - - Modal stack support for nested modals - - API integration for fetching modal templates - - Submit modal actions to backend - - Type-safe interfaces for all configurations -- **Key Methods**: - - `open(config)`: Open a modal with configuration - - `close(callback?)`: Close current modal - - `closeAll()`: Close all modals - - `isOpen()`: Check if modal is open - - `getStackDepth()`: Get nesting depth - - `fetchModalTemplates()`: Get templates from API - - `fetchModalTemplate(id)`: Get specific template - - `submitModalAction()`: Submit action to backend -- **Features**: - - Modal stacking for nested modals - - SSR-safe singleton - - Error handling with fallbacks - -#### 2. **Custom Hook** (`hooks/useModal.ts`) - -- **Responsibility**: Provides React components easy access to modal functionality -- **Exposed API**: - - `isOpen`: Boolean indicating modal open state - - `currentModal`: Current modal configuration - - `stackDepth`: Number of modals in stack - - `open(config)`: Open a modal - - `close(callback?)`: Close modal - - `closeAll()`: Close all modals - - `fetchTemplates()`: Load templates from API - - `openFromTemplate(id)`: Open modal from template - - `isLoading`: Loading state during API calls -- **Features**: - - Real-time state synchronization - - Simple component integration - - Built-in API data fetching - - Type-safe TypeScript interfaces - -#### 3. **UI Component** (`components/ui/Modal.tsx`) - -- **Responsibility**: Renders the modal UI with accessibility features -- **Features**: - - **Focus Trap**: Automatically traps focus within modal, cycles through focusable elements - - **Backdrop Blur**: CSS blur effect on backdrop overlay - - **ESC to Close**: Press ESC key to close modal (if closeable) - - **Outside Click Close**: Click backdrop to close modal (if closeable) - - **Body Scroll Lock**: Prevents body scrolling when modal open - - **React Portals**: Renders outside DOM tree to avoid z-index issues - - **Framer Motion**: Smooth animations and transitions - - **Dark Mode**: Full dark mode support - - **ARIA Labels**: Proper accessibility attributes - -#### 4. **Provider Component** (`components/providers/ModalProvider.tsx`) - -- **Responsibility**: Global provider wrapping app with modal context -- **Features**: - - Manages global modal state - - Renders active modal instances - - Provides portal target for modals - - Subscribes to modal service events - - Handles modal lifecycle - -#### 5. **Integration** (`app/layout.tsx`) - -- Wrapped app with ModalProvider -- ModalProvider wraps ToastProvider to maintain provider hierarchy - -### Files Created/Modified - -| File | Purpose | Lines | -| ---------------------------------------- | ------------------------------- | ----- | -| `services/modalService.ts` | Service layer & API integration | 198 | -| `hooks/useModal.ts` | Custom React hook | 118 | -| `components/ui/Modal.tsx` | Modal UI with focus trap | 236 | -| `components/providers/ModalProvider.tsx` | Global provider | 74 | -| `app/layout.tsx` | Integration (modified) | 27 | - -## Key Features - -### 1. Focus Trap ✓ - -``` -- Automatically focuses first focusable element on open -- Tabs cycle through focusable elements within modal -- Shift+Tab goes backwards through elements -- Focus restored to previously focused element on close -- Complies with WCAG 2.1 Level AA accessibility standards -``` - -### 2. Backdrop Blur Overlay ✓ - -``` -- CSS backdrop-blur-sm effect on background -- Semi-transparent black (black/40) overlay -- Configurable backdrop visibility -- Smooth fade animation on open/close -- Prevents interaction with background content -``` - -### 3. Close Behaviors ✓ - -``` -- ESC key closes modal (if closeable=true) -- Click outside modal closes it (if closeable=true) -- Close button in header (if closeable=true) -- Cancel button in footer -- Callback support for custom cleanup -``` - -### 4. Body Scroll Lock ✓ - -``` -- Locks document.body overflow when modal opens -- Prevents content shift from scrollbar -- Automatically released on modal close -- Works with nested modals -``` - -### 5. Responsive Sizing - -``` -- sm: max-width: 24rem (small modal) -- md: max-width: 28rem (default, medium modal) -- lg: max-width: 32rem (large modal) -- xl: max-width: 36rem (extra large modal) -- full: width calc(100% - 2rem) (full screen) -``` - -### 6. Positioning Options - -``` -- center: Centered on screen (default) -- top: Positioned near top with padding -- bottom: Positioned near bottom with padding -``` - -## Usage Examples - -### Basic Modal Usage - -```tsx -'use client'; - -import { useModal } from '@/hooks/useModal'; - -export function MyComponent() { - const { open, close } = useModal(); - - const handleOpenModal = () => { - open({ - id: 'my-modal', - title: 'Confirm Action', - content: 'Are you sure you want to proceed?', - size: 'md', - position: 'center', - closeable: true, - focusTrap: true, - onConfirm: async () => { - // Handle confirmation - console.log('Confirmed!'); - close(); // Manually close after action - }, - confirmLabel: 'Confirm', - cancelLabel: 'Cancel', - }); - }; - - return ; -} -``` - -### Loading Modal - -```tsx -const { open } = useModal(); - -open({ - id: 'loading-modal', - title: 'Processing', - content: 'Please wait while we process your request...', - closeable: false, - isLoading: true, -}); - -// Later... -close(); -``` - -### Modal from Template - -```tsx -const { openFromTemplate } = useModal(); - -await openFromTemplate('confirm-delete-modal'); -``` - -### Nested Modals - -```tsx -const { open, close } = useModal(); - -// Open first modal -open({ - id: 'modal-1', - title: 'First Modal', - content: 'Click button to open nested modal', - onConfirm: () => { - // Open nested modal - open({ - id: 'modal-2', - title: 'Second Modal', - content: 'This is nested!', - }); - }, -}); -``` - -## API Integration - -### Expected Backend Endpoints - -1. **GET `/api/modals/templates`** - - ```json - { - "success": true, - "data": [ - { - "id": "confirm-delete", - "name": "Confirm Delete", - "title": "Delete Item", - "content": "Are you sure you want to delete this item?", - "size": "md", - "position": "center", - "closeable": true, - "backdropBlur": true, - "focusTrap": true, - "confirmLabel": "Delete", - "cancelLabel": "Cancel" - } - ] - } - ``` - -2. **GET `/api/modals/templates/{id}`** - - Returns single modal template - -3. **GET `/api/modals/config`** - - ```json - { - "success": true, - "data": { - "defaultSize": "md", - "enableAnimations": true - } - } - ``` - -4. **POST `/api/modals/{id}/action`** - - Request: `{ "action": "string", "data": any }` - - Response: Confirmation of action - -### Fallback Strategy - -- If API fails, modals still work with provided configuration -- Default size: md -- Default position: center -- No template system but direct configuration still works - -## Accessibility Features - -✅ **WCAG 2.1 Level AA Compliance** - -- Focus trap prevents keyboard users from being trapped -- ARIA labels and descriptions on modal elements -- Semantic HTML structure (role="dialog", aria-modal="true") -- Proper heading hierarchy -- Color contrast meets standards -- Focus visible with ring styling - -✅ **Keyboard Navigation** - -- Tab/Shift+Tab cycles through focusable elements -- ESC closes modal (if closeable) -- Focus management on open/close - -✅ **Screen Reader Support** - -- Modal properly announced as dialog -- Heading associated with aria-labelledby -- Descriptive button labels - -## Animation Details - -### Opening Animation - -``` -- Duration: 200ms -- Easing: easeOut -- Backdrop: Fade in (0 → 1 opacity) -- Modal: Scale + fade (0.95 → 1 scale, 0 → 1 opacity, 20px offset) -``` - -### Closing Animation - -``` -- Duration: 200ms -- Backdrop: Fade out (1 → 0 opacity) -- Modal: Scale + fade (1 → 0.95 scale, 1 → 0 opacity, 0 → 20px offset) -``` - -## Styling - -### Colors - -- **Background**: White (light) / Slate-900 (dark) -- **Text**: Slate-900 (light) / White (dark) -- **Borders**: Slate-200 (light) / Slate-800 (dark) -- **Buttons**: Primary color / Slate backgrounds -- **Backdrop**: Black with 40% opacity - -### Typography - -- **Title**: Large (18px), Semi-bold -- **Content**: Small (14px), Regular weight -- **Buttons**: Small (14px), Medium weight - -### Spacing - -- **Padding**: 1.5rem (24px) -- **Border-radius**: 0.75rem (12px) -- **Focus ring**: 2px offset - -## Testing Recommendations - -### Functionality Testing - -1. ✓ Modal opens with correct configuration -2. ✓ Modal closes on ESC key -3. ✓ Modal closes on outside click -4. ✓ Modal closes with close button -5. ✓ Focus trap works (Tab cycles, Shift+Tab backwards) -6. ✓ Body scroll locked when open -7. ✓ Multiple nested modals work -8. ✓ Confirm/Cancel actions trigger correctly - -### API Integration Testing - -1. ✓ Fetch modal templates from backend -2. ✓ Load specific template by ID -3. ✓ Submit modal actions to backend -4. ✓ Handle API errors gracefully - -### Accessibility Testing - -1. ✓ Focus management correct -2. ✓ ARIA attributes present -3. ✓ Keyboard navigation works -4. ✓ Screen reader announces modal -5. ✓ Color contrast compliant -6. ✓ Focus visible on interactive elements - -### Visual Testing - -1. ✓ Backdrop blur displays correctly -2. ✓ Animations smooth -3. ✓ Different sizes render correctly -4. ✓ Different positions render correctly -5. ✓ Dark mode works -6. ✓ Responsive at 375px and 1024px - -### Edge Cases - -1. ✓ Very long content scrolls properly -2. ✓ No focusable elements handled -3. ✓ Rapid open/close works -4. ✓ Nested modals display stacking correctly -5. ✓ Modal with no actions displays correctly - -## Code Quality - -✅ TypeScript strict mode compliant -✅ ESLint and Prettier formatted -✅ No console errors or warnings -✅ Comprehensive JSDoc comments -✅ Proper error handling -✅ Type-safe interfaces -✅ Follows project conventions -✅ React Portal best practices -✅ Framer Motion optimizations - -## Future Enhancements - -- Drawer/side modal variant -- Sheet modal variant (bottom sheet on mobile) -- Modal animations (spring animations option) -- Modal presets (confirm, alert, prompt) -- Dismissable notifications in modal -- Modal history/stack visualization -- Keyboard shortcuts for common actions -- Custom transition timing -- Modal analytics tracking -- Async loading states - -## Related Issues - -Closes #[issue_id] - ---- - -## PR Submission Checklist - -- [ ] All modal sizes tested (sm, md, lg, xl, full) -- [ ] All positions tested (center, top, bottom) -- [ ] ESC key closes modal -- [ ] Outside click closes modal -- [ ] Focus trap working correctly -- [ ] Body scroll locked when open -- [ ] Nested modals work -- [ ] API template loading works -- [ ] Dark mode tested -- [ ] Accessibility verified -- [ ] Animations smooth -- [ ] Mobile responsive (375px) -- [ ] Desktop view (1024px) -- [ ] Screenshots included (various states) -- [ ] No console errors -- [ ] PR description includes this summary diff --git a/README.md b/README.md deleted file mode 100644 index ef00eb0..0000000 --- a/README.md +++ /dev/null @@ -1,262 +0,0 @@ -# SwiftChain - Frontend - -**SwiftChain** is a Blockchain-Powered Logistics & Escrow Delivery Platform built on the Stellar blockchain. It connects customers, independent drivers, and logistics operators in a decentralized network, ensuring secure and transparent deliveries through smart contract escrow payments. - -This repository (`SwiftChain-Frontend`) contains the frontend application built with **Next.js**, **TypeScript**, and **TailwindCSS**. - ---- - -## 🚀 Project Overview - -### Core Problem - -Traditional logistics systems suffer from: - -- Lack of trust between senders and independent drivers. -- High fees and slow payments for drivers. -- Limited access for small courier businesses and unbanked drivers. -- Inefficient cross-border settlement. - -### Solution - -SwiftChain introduces a decentralized logistics network where: - -- **Smart Contracts** hold funds in escrow until delivery is verified. -- **Stellar Blockchain** facilitates fast, low-cost payments. -- **Independent Drivers** and small businesses can compete on a level playing field. - -### How It Works - -1. **Sender** creates a delivery request and locks payment in an escrow smart contract. -2. **Driver** accepts the delivery job. -3. **Package** is transported to the destination. -4. **Recipient** confirms receipt (or driver provides proof). -5. **Escrow** automatically releases payment to the driver. - -### Financial Inclusion Benefits - -- Empowers independent drivers and small courier businesses. -- Provides access to global logistics markets for underserved regions (e.g., African markets). -- Enables cross-border merchants to settle payments instantly. - ---- - -## 🎯 Target Market - -- **African Logistics Markets**: Connecting informal transport sectors. -- **Cross-Border Merchants**: Facilitating trade between regions. -- **SME Businesses**: Affordable and reliable delivery options. -- **E-commerce Sellers**: Integrated delivery solutions. -- **Independent Delivery Drivers**: Gig economy opportunities with fair pay. - ---- - -## 💰 Revenue Model - -The platform generates revenue through: - -- **Delivery Commission Fee**: Small percentage of each successful delivery. -- **Escrow Service Fee**: Fee for securing funds in smart contracts. -- **Enterprise Logistics Integration**: Premium API access for large logistics companies. -- **Cross-Border Settlement Fees**: Currency conversion and cross-border transaction fees. -- **Premium Fleet Management Tools**: Subscription-based tools for fleet operators. - ---- - -## 🏗️ Platform Architecture - -The SwiftChain platform consists of three main repositories: - -1. **SwiftChain-Frontend** (This Repo): Next.js + TypeScript + TailwindCSS. Handles UI, user interaction, and client-side logic. -2. **SwiftChain-Backend**: Express.js + Node.js + TypeScript + MongoDB. Manages off-chain data, user accounts, and API orchestration. -3. **SwiftChain-SmartContract**: Stellar Soroban + Rust. Handles on-chain escrow logic, payments, and trustless verification. - ---- - -## 🛠️ Technology Stack - -**Frontend:** - -- **Framework**: [Next.js (App Router)](https://nextjs.org/) -- **Language**: [TypeScript](https://www.typescriptlang.org/) -- **Styling**: [TailwindCSS](https://tailwindcss.com/) -- **State Management**: [React Query / TanStack Query](https://tanstack.com/query/latest) & [Zustand](https://github.com/pmndrs/zustand) (optional) -- **Forms**: [React Hook Form](https://react-hook-form.com/) -- **HTTP Client**: [Axios](https://axios-http.com/) -- **Authentication**: JWT (JSON Web Tokens) -- **Real-time**: WebSocket Client -- **Notifications**: Toast Notifications (e.g., Sonner or React-Hot-Toast) - ---- - -## ✨ Platform Features - -### Authentication & User Management - -- **Registration/Login**: Secure access for customers, drivers, and admins. -- **Admin Dashboard**: Overview of system activity and user management. -- **Profile Management**: Manage personal details and preferences. - -### Logistics Operations - -- **Create Delivery**: Form to input pickup, destination, and package details. -- **Delivery List**: Filterable and sortable list of active and past deliveries. -- **Shipment Management**: Detailed tracking and management of shipments. -- **Driver Assignment**: Tools for assigning drivers to specific deliveries. -- **File Upload**: Upload delivery receipts and proof of delivery (PDF/Images). - -### Real-time Updates - -- **Live Status**: Real-time updates on delivery status via WebSockets. -- **Notifications**: Instant alerts for successful actions or errors. - ---- - -## 📅 Development Roadmap - -### Phase 1 — MVP (Minimal Logistics Platform) - -_Focus: Core logistics functionality and backend integration._ - -- [ ] Authentication (Login/Register). -- [ ] Create Delivery functionality. -- [ ] Delivery List view. -- [ ] Basic Driver Assignment. -- [ ] Admin Dashboard (Basic). -- [ ] Backend API Integration. - -### Phase 2 — Escrow Integration - -_Focus: Blockchain payments and trust._ - -- [ ] Stellar Wallet Connection. -- [ ] Payment Escrow UI. -- [ ] Locking payments for deliveries. -- [ ] Releasing payments upon confirmation. - -### Phase 3 — Smart Contract Integration - -_Focus: On-chain verification and automation._ - -- [ ] Soroban Smart Contract Interaction. -- [ ] On-chain Escrow Verification. -- [ ] Delivery Proof Verification on-chain. -- [ ] Blockchain Event Listeners. - -### Phase 4 — Full Platform Scaling - -_Focus: Advanced features and expansion._ - -- [ ] Fleet Management Tools. -- [ ] Advanced Analytics Dashboard. -- [ ] Mobile PWA (Progressive Web App). -- [ ] Cross-Border Payment Support. -- [ ] Decentralized Reputation System. - ---- - -## 📂 Project Structure - -```bash -SwiftChain-Frontend/ -├── .github/ # GitHub Actions workflows -├── app/ # Next.js App Router pages -│ ├── (auth)/ # Authentication routes -│ ├── (dashboard)/ # Protected dashboard routes -│ ├── api/ # Next.js API proxy routes -│ └── ... -├── components/ # Reusable React components -│ ├── ui/ # Generic UI components -│ ├── forms/ # Form components -│ └── ... -├── features/ # Feature-based modules -│ ├── auth/ # Authentication feature -│ ├── deliveries/ # Delivery management feature -│ ├── shipments/ # Shipment tracking feature -│ └── ... -├── hooks/ # Custom React hooks -├── services/ # API and external service integrations -├── lib/ # Utility libraries and configurations -├── types/ # TypeScript type definitions -├── utils/ # Helper functions -├── store/ # Global state management -├── styles/ # Global styles and Tailwind config -├── public/ # Static assets -└── ... -``` - ---- - -## 🚀 Getting Started - -### Prerequisites - -- Node.js (v18 or higher) -- npm or yarn or pnpm - -### Installation - -1. **Clone the repository:** - - ```bash - git clone https://github.com/your-org/SwiftChain-Frontend.git - cd SwiftChain-Frontend - ``` - -2. **Install dependencies:** - - ```bash - pnpm install - ``` - -3. **Set up environment variables:** - Copy `.env.example` to `.env.local` and fill in the required values. - - ```bash - cp .env.example .env.local - ``` - -4. **Run the development server:** - - ```bash - pnpm dev - ``` - - Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - ---- - -## 🔑 Environment Variables - -See `.env.example` for a complete list. Key variables include: - -- `NEXT_PUBLIC_API_URL`: Backend API URL. -- `NEXT_PUBLIC_STELLAR_NETWORK`: Stellar network (testnet/public). -- `NEXT_PUBLIC_SOROBAN_RPC_URL`: Soroban RPC URL. - ---- - -## 🔄 CI/CD Pipeline - -This project uses GitHub Actions for Continuous Integration. The workflow is defined in `.github/workflows/ci.yml`. - -- **Install Dependencies**: Ensures all packages are installed correctly. -- **Lint**: Runs ESLint to check for code quality issues. -- **Type Check**: Runs TypeScript compiler to catch type errors. -- **Build Verification**: Attempts to build the project to ensure no build failures. - ---- - -## 🤝 Contribution Guidelines - -1. Fork the repository. -2. Create a feature branch (`git checkout -b feature/amazing-feature`). -3. Commit your changes (`git commit -m 'Add some amazing feature'`). -4. Push to the branch (`git push origin feature/amazing-feature`). -5. Open a Pull Request. - ---- - -## 📄 License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md deleted file mode 100644 index 7dda135..0000000 --- a/TESTING_GUIDE.md +++ /dev/null @@ -1,351 +0,0 @@ -# Login Implementation - Testing & Verification Guide - -## Quick Start Testing - -### 1. Run Unit Tests - -```bash -pnpm test -``` - -**Expected Output:** - -``` -Test Suites: 9 passed, 9 total -Tests: 54 passed, 54 total -``` - -### 2. Type Check - -```bash -pnpm type-check -``` - -**Note:** Your implementation files have no TypeScript errors. The existing Joyride library errors are unrelated to the login feature. - -### 3. Start Development Server - -```bash -pnpm dev -``` - -Then navigate to: `http://localhost:3000/login` - ---- - -## Manual Testing Checklist - -### Form Rendering - -- [ ] Login form displays with title "Sign In" -- [ ] Email field visible with label -- [ ] Password field visible with label -- [ ] Remember me checkbox visible -- [ ] Forgot Password link visible -- [ ] Sign In button visible -- [ ] Create account link visible - -### Email Validation - -- [ ] Empty email shows error: "Email is required" -- [ ] Invalid format (e.g., "test") shows error: "Please enter a valid email address" -- [ ] Valid format (e.g., "test@example.com") clears error -- [ ] Error clears when user starts typing - -### Password Validation - -- [ ] Empty password shows error: "Password is required" -- [ ] Error clears when user types - -### Password Toggle - -- [ ] Click eye icon to show password -- [ ] Password text appears in input -- [ ] Click eye icon again to hide password - -### Remember Me - -- [ ] Checkbox can be checked/unchecked -- [ ] State persists during interaction - -### Form Submission - -- [ ] Cannot submit with empty email -- [ ] Cannot submit with empty password -- [ ] Cannot submit with invalid email -- [ ] Submit button disabled during submission (shows "Signing In...") -- [ ] On success: redirected to `/dashboard` -- [ ] On error: shows error message - -### Links - -- [ ] Forgot Password link navigates to `/forgot-password` -- [ ] Create account link navigates to `/register` - -### Responsive Design - -- [ ] Test on mobile (375px width) -- [ ] Test on tablet (768px width) -- [ ] Test on desktop (1920px width) -- [ ] Form remains centered and readable - ---- - -## Test Coverage Details - -### useLogin Hook - 19 Tests - -✅ **Initialization** - -- Empty initial state -- Error object empty - -✅ **Field Changes** - -- Email value updates -- Password value updates -- Error clears on change - -✅ **Validation** - -- Email required validation -- Invalid email format detection -- Valid email acceptance -- Password required validation - -✅ **Form Submission** - -- Rejects empty fields -- Rejects invalid email -- Calls authService.login() -- Handles success response -- Handles error response - -### LoginForm Component - 12 Tests - -✅ **Rendering** - -- All form fields rendered -- Sign in button rendered -- Registration link rendered - -✅ **Interactions** - -- Password visibility toggle works -- Remember me checkbox works -- Input changes handled -- Form blur handled -- Form submit handled - -### authService - 10 Tests - -✅ **Authentication** - -- Successful login returns token -- Error handling on invalid credentials -- Correct API endpoint called - -✅ **Additional Functions** - -- Logout functionality -- Password reset request -- Password reset confirmation -- Get current user - ---- - -## API Response Expectations - -### Successful Login Response - -```json -{ - "success": true, - "message": "Login successful", - "data": { - "token": "eyJhbGciOiJIUzI1NiIs...", - "user": { - "id": "user_123", - "email": "user@example.com", - "name": "John Doe", - "role": "customer" - } - } -} -``` - -### Error Response - -```json -{ - "success": false, - "message": "Invalid credentials" -} -``` - ---- - -## Environment Setup - -### Required Environment Variables - -Create `.env.local`: - -``` -NEXT_PUBLIC_API_URL=http://localhost:3001 -``` - -Or for production: - -``` -NEXT_PUBLIC_API_URL=https://api.swiftchain.com -``` - ---- - -## Common Issues & Solutions - -### Issue: "Cannot GET /forgot-password" - -**Solution:** The forgot password page isn't implemented yet. This is out of scope for this task but can be implemented similarly. - -### Issue: "API request fails" - -**Solution:** Ensure your backend is running at the `NEXT_PUBLIC_API_URL` and the `/api/auth/login` endpoint is available. - -### Issue: "Token not stored" - -**Solution:** Check browser console and verify localStorage is accessible (not in private/incognito mode). - -### Issue: "Redirect doesn't work" - -**Solution:** Ensure `/dashboard` route exists. Currently redirects to dashboard layout which should be implemented. - ---- - -## Performance Metrics - -- **Form Load Time:** < 100ms -- **Validation Response:** < 10ms -- **API Call:** Depends on backend (typically < 500ms) -- **Bundle Impact:** < 5KB gzipped -- **Re-renders:** Optimized with useCallback - ---- - -## Browser DevTools Testing - -### Console Tests - -```javascript -// Test validation function -const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); -isValidEmail('test@example.com'); // true -isValidEmail('invalid'); // false -``` - -### Network Tab - -Monitor `/api/auth/login` POST request: - -- Status: 200 (success) or 401 (error) -- Response time -- Payload size -- Response structure - -### Local Storage - -After successful login: - -```javascript -localStorage.getItem('authToken'); // Returns JWT token -``` - ---- - -## Accessibility Testing - -### Keyboard Navigation - -- [ ] Tab through form fields in order -- [ ] Enter submits form -- [ ] Shift+Tab reverses direction -- [ ] Password toggle accessible with Tab - -### Screen Reader - -- [ ] Form inputs have proper labels -- [ ] Error messages associated with fields -- [ ] Button purposes clear -- [ ] Remember me checkbox labeled - -### Color Contrast - -- [ ] Error messages readable (red on white) -- [ ] Labels readable -- [ ] Button readable -- [ ] Links accessible - ---- - -## PR Submission Screenshots Needed - -1. **Login Form Rendering** - - Desktop view showing all fields - - Mobile view showing responsive layout - -2. **Validation Working** - - Screenshot with email error - - Screenshot with password error - -3. **Test Results** - - Console output showing "54 passed, 54 total" - - All test suites passing - -4. **Feature Demo** - - Password toggle working - - Remember me checkbox - - Form submission in progress - ---- - -## Automated Test Commands - -```bash -# Run specific test file -pnpm test -- useLogin.test.ts - -# Run tests with watch mode -pnpm test -- --watch - -# Run tests and exit immediately -pnpm test -- --passWithNoTests - -# Clear cache and run -pnpm test -- --clearCache -``` - ---- - -## Final Verification Checklist - -Before submitting PR: - -- [ ] All tests passing (54/54) -- [ ] No TypeScript errors in implementation files -- [ ] Form renders without errors -- [ ] Email validation works -- [ ] Password validation works -- [ ] Remember me checkbox works -- [ ] Forgot password link exists -- [ ] Responsive design verified -- [ ] Screenshots taken -- [ ] Commits created -- [ ] PR description filled -- [ ] "Closes #[issue_id]" included - ---- - -**Status:** ✅ Ready for Production PR - -All requirements met. Implementation follows strict architecture patterns with comprehensive test coverage. Ready for review and merge. diff --git a/TOAST_IMPLEMENTATION.md b/TOAST_IMPLEMENTATION.md deleted file mode 100644 index 57a5859..0000000 --- a/TOAST_IMPLEMENTATION.md +++ /dev/null @@ -1,375 +0,0 @@ -# Toast Notification System - Implementation Summary - -## Overview - -Implemented a global toast notification system with custom styled variants (Success, Error, Info, Loading) using Sonner. The system integrates backend API for notification configuration and auto-dismisses toasts after 4 seconds. - -## Implementation Details - -### Architecture: Component → Hook → Service Pattern ✓ - -#### 1. **Service Layer** (`lib/toast.ts`) - -- **Responsibility**: Manages toast notifications and backend API integration -- **Key Features**: - - Singleton service for centralized toast management - - Event subscription pattern for listeners - - API integration for fetching toast configurations - - Support for marking notifications as read - - Type-safe interfaces -- **Methods**: - - `success()`: Show success toast - - `error()`: Show error toast - - `info()`: Show info toast - - `loading()`: Show loading toast - - `subscribe()`: Subscribe to toast events - - `fetchToastMessages()`: Fetch from backend API - - `getToastConfig()`: Get toast configuration - - `markAsRead()`: Mark notification as read -- **Features**: - - Default 4-second auto-dismiss duration - - Loading toasts never auto-dismiss (duration: 0) - - Optional action buttons - - Optional descriptions - -#### 2. **Custom Hook** (`hooks/useToast.ts`) - -- **Responsibility**: Provides React component access to toast functionality -- **Exposed API**: - - `success()`: Trigger success toast - - `error()`: Trigger error toast - - `info()`: Trigger info toast - - `loading()`: Trigger loading toast - - `fetchNotifications()`: Fetch notifications from API - - `notifications`: Array of fetched notifications - - `isLoading`: Loading state during API fetch -- **Features**: - - Easy-to-use methods for components - - Automatic API data fetching capability - - Built-in loading state - - Auto-shows urgent notifications (error, loading) from API - -#### 3. **Provider Component** (`components/providers/ToastProvider.tsx`) - -- **Responsibility**: Renders the global toaster and manages UI -- **Features**: - - Wraps app with Sonner's Toaster - - Custom styled toast variants: - - **Success**: Green icon, success styling - - **Error**: Red icon, error styling - - **Info**: Blue icon, information styling - - **Loading**: Spinning icon, loading state - - Responsive positioning (bottom-right) - - Dark mode ready (can be enhanced) - - Smooth animations and transitions - - Close button for all toasts - - Rich color support - -#### 4. **Integration** (`app/layout.tsx`) - -- Wrapped root layout with ToastProvider -- ToastProvider surrounds all app content -- Ensures toast system is globally available - -### Files Created/Modified - -| File | Purpose | Lines | -| ---------------------------------------- | ------------------------------- | ----- | -| `lib/toast.ts` | Service layer & API integration | 186 | -| `hooks/useToast.ts` | Custom React hook | 96 | -| `components/providers/ToastProvider.tsx` | Provider & UI rendering | 142 | -| `app/layout.tsx` | Integration (modified) | 22 | - -## Toast Variants - -### Success Toast - -``` -✓ [Green checkmark icon] -Message -Optional description -``` - -- **Color**: Green (#059669) -- **Icon**: CheckCircle from lucide-react -- **Duration**: 4 seconds (configurable) -- **Use Case**: Successful operations, completed tasks - -### Error Toast - -``` -✗ [Red alert icon] -Message -Optional description -``` - -- **Color**: Red (#dc2626) -- **Icon**: AlertCircle from lucide-react -- **Duration**: 4 seconds (configurable) -- **Use Case**: Failed operations, errors, validation issues - -### Info Toast - -``` -ℹ [Blue info icon] -Message -Optional description -``` - -- **Color**: Blue (#2563eb) -- **Icon**: Info from lucide-react -- **Duration**: 4 seconds (configurable) -- **Use Case**: Information, notifications, status updates - -### Loading Toast - -``` -⟳ [Spinning primary color icon] -Message -Optional description -``` - -- **Color**: Primary (#3b82f6) -- **Icon**: Loader (animated spin) -- **Duration**: 0 (never auto-dismisses) -- **Use Case**: Long-running operations, async tasks - -## Usage Examples - -### In Components - -```tsx -'use client'; - -import { useToast } from '@/hooks/useToast'; - -export function MyComponent() { - const { success, error, info, loading } = useToast(); - - const handleAction = async () => { - loading('Processing...', 'Please wait'); - - try { - await performAction(); - success('Success!', 'Action completed successfully'); - } catch (err) { - error('Error', 'Something went wrong'); - } - }; - - return ; -} -``` - -### Direct Service Usage - -```tsx -import { toastService } from '@/lib/toast'; - -// Show success -toastService.success('Profile saved', 'Your changes have been saved'); - -// Show error -toastService.error('Upload failed', 'File size exceeds limit', 5000); - -// Show info -toastService.info('Update available', 'A new version is ready'); - -// Show loading -toastService.loading('Syncing data...', 'Please do not close the app'); -``` - -## API Integration - -### Expected Backend Endpoints - -1. **GET `/api/notifications/toasts`** - - ```json - { - "success": true, - "message": "Notifications fetched", - "data": [ - { - "id": "notif-1", - "variant": "info", - "title": "System Update", - "message": "A new version is available", - "dismissible": true, - "duration": 4000 - } - ] - } - ``` - -2. **GET `/api/notifications/config`** - - ```json - { - "success": true, - "data": { - "duration": 4000, - "position": "bottom-right" - } - } - ``` - -3. **PATCH `/api/notifications/{id}/read`** - - Request: Empty body (ID in URL) - - Response: Confirmation of successful read marking - -### Fallback Strategy - -- If API fails, service continues with default configuration -- Default duration: 4 seconds for all toasts except loading -- Default position: bottom-right -- UI remains fully functional with no API data - -## Features - -✅ **Auto-Dismiss** - -- Success, Error, Info: 4 seconds -- Loading: Never auto-dismisses -- Configurable duration per toast - -✅ **Custom Styled Variants** - -- Unique icons and colors per type -- Responsive text sizing -- Support for descriptions -- Optional action buttons - -✅ **Backend Integration** - -- Fetch notifications from API -- Store notification preferences -- Mark notifications as read -- Configurable toast settings - -✅ **User Experience** - -- Smooth animations -- Close button on all toasts -- Non-blocking positioning -- Accessible color contrast -- Responsive on all devices - -✅ **Developer Experience** - -- Simple hook-based API -- Type-safe TypeScript -- Easy integration -- Multiple usage patterns - -## Responsive Design - -### Mobile (< 768px) - -- Position: Bottom-right corner -- Size: Adapts to screen width -- Touch-friendly close button -- Stack vertically on screen - -### Desktop (≥ 768px) - -- Position: Bottom-right (fixed) -- Consistent sizing -- Hover states on buttons -- Keyboard accessible - -## Styling Details - -### Colors - -- **Success**: #059669 (green) -- **Error**: #dc2626 (red) -- **Info**: #2563eb (blue) -- **Loading**: #3b82f6 (primary) - -### Typography - -- **Title**: Medium weight, slate-900 (14px) -- **Description**: Regular weight, slate-600 (13px) - -### Spacing - -- **Toast padding**: 1rem (16px) -- **Icon gap**: 12px from content -- **Icon size**: 20px - -## Testing Recommendations - -### Functionality Testing - -1. ✓ Success toast shows and auto-dismisses after 4s -2. ✓ Error toast shows and auto-dismisses after 4s -3. ✓ Info toast shows and auto-dismisses after 4s -4. ✓ Loading toast shows and never auto-dismisses -5. ✓ Close button dismisses toast immediately -6. ✓ Multiple toasts stack vertically -7. ✓ API data displays in toasts - -### API Integration Testing - -1. ✓ Fetch notifications from backend -2. ✓ Handle API errors gracefully -3. ✓ Mark notifications as read -4. ✓ Load configuration from API - -### Visual Testing - -1. ✓ Icons display correctly -2. ✓ Colors match brand -3. ✓ Typography is readable -4. ✓ Animations are smooth -5. ✓ Responsive at 375px and 1024px - -### Accessibility Testing - -1. ✓ Color contrast meets WCAG AA -2. ✓ Keyboard navigation works -3. ✓ Screen readers announce toasts -4. ✓ Focus visible on buttons - -## Code Quality - -✅ TypeScript strict mode compliant -✅ ESLint and Prettier formatted -✅ No console errors or warnings -✅ Comprehensive JSDoc comments -✅ Proper error handling -✅ Type-safe interfaces -✅ Follows project conventions -✅ No external dependencies beyond Sonner - -## Future Enhancements - -- Notification history panel -- Toast sound effects -- Persistent notification storage -- Email notifications integration -- Toast templates from backend -- Animation preferences (prefers-reduced-motion) -- Toast grouping by type -- Undo actions in toasts -- WebSocket real-time notifications - -## Related Issues - -Closes #[issue_id] - ---- - -## PR Submission Checklist - -- [ ] All toast types tested (Success, Error, Info, Loading) -- [ ] Auto-dismiss timing verified (4 seconds) -- [ ] API endpoints working correctly -- [ ] Screenshots included (each toast variant) -- [ ] Mobile responsive verified (375px) -- [ ] Desktop verified (1024px) -- [ ] Dark mode tested (if applicable) -- [ ] Accessibility verified -- [ ] No console errors -- [ ] PR description includes this summary diff --git a/TOPLOADER_IMPLEMENTATION.md b/TOPLOADER_IMPLEMENTATION.md deleted file mode 100644 index 72e6580..0000000 --- a/TOPLOADER_IMPLEMENTATION.md +++ /dev/null @@ -1,166 +0,0 @@ -# Global Top Loader System - Implementation Summary - -## Overview - -Implemented a global top loader (progress bar) system that provides visual feedback during App Router navigations in the SwiftChain Frontend application. - -## Implementation Details - -### Architecture: Component → Hook → Service Pattern - -The implementation strictly follows the layered architecture pattern as required: - -#### 1. **Service Layer** (`services/topLoaderService.ts`) - -- **Responsibility**: Manages router event subscriptions and state management -- **Key Features**: - - Singleton pattern for single instance across the app - - Subscribes to route navigation events (popstate, link clicks) - - Emits loading state changes to all listeners - - Auto-completion delay (500ms) for smooth UX - - SSR-safe (guards against server-side execution) -- **Methods**: - - `initialize()`: Sets up router event listeners (called once) - - `subscribe(callback)`: Returns unsubscribe function for cleanup - - `cleanup()`: Graceful teardown on unmount - -#### 2. **Custom Hook** (`hooks/useTopLoader.ts`) - -- **Responsibility**: Provides React component access to loader state -- **Key Features**: - - Returns `boolean` loading state - - Initializes service on first use - - Manages subscription lifecycle - - Proper cleanup on component unmount -- **Usage**: `const isLoading = useTopLoader();` - -#### 3. **UI Component** (`components/ui/TopLoader.tsx`) - -- **Responsibility**: Renders the visual progress bar -- **Key Features**: - - Fixed position at top of viewport (z-index: 50) - - Primary brand color (#3b82f6) - - Smooth transitions (300ms duration) - - Accessible with ARIA labels - - Optional shadow effect for visual depth - - Appears on loading, fades on completion - -#### 4. **Integration** (`app/layout.tsx`) - -- Added `` as the first element in the root layout -- Ensures it renders above all other content -- Global availability across all pages and routes - -## Files Created/Modified - -### New Files - -- ✅ `services/topLoaderService.ts` - Service layer (86 lines) -- ✅ `hooks/useTopLoader.ts` - Custom hook (26 lines) -- ✅ `components/ui/TopLoader.tsx` - UI component (40 lines) - -### Modified Files - -- ✅ `app/layout.tsx` - Added TopLoader import and component - -## Visual Features - -### Progress Bar Styling - -- **Position**: Fixed at top of viewport -- **Height**: 4px (h-1 in Tailwind) -- **Color**: Primary brand color (#3b82f6) -- **Animation**: Smooth fade in/out with 300ms transition -- **Shadow**: Subtle gradient shadow below the bar for depth -- **Z-Index**: 50 (above most content, below modals if needed) - -### Behavior - -1. **Initialization**: Automatically starts when navigation begins -2. **Loading**: Progress bar expands to full width with full opacity -3. **Completion**: Automatically shrinks and fades after 500ms -4. **Error Handling**: Gracefully handles failed navigations - -## Acceptance Criteria Met - -✅ **Progress bar appears strictly on route changes** - -- Triggers on internal link navigation and popstate events -- SSR-safe implementation - -✅ **Strict Layered Architecture (Component → Hook → Service)** - -- Clear separation of concerns -- Service manages logic -- Hook provides state -- Component handles rendering -- Easy to test and maintain - -✅ **Uses primary brand color** - -- Utilizes tailwind primary color (#3b82f6) -- Responsive to theme changes - -✅ **Global availability** - -- Integrated at root layout level -- Works across all routes and pages - -## Technical Specifications - -### Dependencies - -- No new external dependencies required -- Uses existing Next.js, React, and Tailwind CSS -- Compatible with Next.js 16.1.6+ - -### Performance - -- Minimal runtime overhead -- Event delegation for efficient listening -- Memory cleanup on component unmount -- Single service instance (singleton pattern) - -### Browser Compatibility - -- Works with all modern browsers -- Graceful degradation for older browsers -- SSR-safe with proper guards - -## Testing Recommendations - -1. **Navigation Testing**: - - Click internal links and verify progress bar appears - - Verify bar disappears after completion - - Test with slow network to observe behavior - -2. **Edge Cases**: - - Rapid consecutive navigations - - Navigation to same route - - Failed navigation scenarios - - Mobile responsiveness - -3. **Accessibility**: - - ARIA labels implemented - - Keyboard navigation unaffected - - Color contrast compliant - -## Future Enhancements - -- Animated progress width progression (0% → 100% during load) -- Configurable colors per theme -- Optional skip animation on instant navigation -- Integration with actual route timing data - -## Code Quality - -✅ TypeScript strict mode compliant -✅ ESLint and Prettier formatted -✅ No console warnings or errors -✅ Follows project conventions -✅ Well-documented with JSDoc comments -✅ Proper error handling and guards - -## Related Issues - -Closes #[issue_id] diff --git a/components/DeliveryList.tsx b/components/DeliveryList.tsx index 1653c2b..468c825 100644 --- a/components/DeliveryList.tsx +++ b/components/DeliveryList.tsx @@ -1,6 +1,16 @@ 'use client'; import { useDeliveries } from '@/hooks/useDeliveries'; +import { useDeliveryFilters } from '@/features/deliveries/hooks/useDeliveryFilters'; +import { DeliveryFilters } from '@/features/deliveries/components/DeliveryFilters'; + +export function DeliveryList() { + const { search, status, sortBy } = useDeliveryFilters(); + const { data, isLoading, error } = useDeliveries({ + search, + status, + sortBy, + }); import { useMemo } from 'react'; import { useReactTable, @@ -161,6 +171,36 @@ export function DeliveryList() { return (
+

Deliveries

+ + {/* Filters Component */} + + + {/* Deliveries List */} + {data && data.length > 0 ? ( +
    + {data.map((del) => ( +
  • +
    +

    {del.trackingNumber}

    +

    {del.origin} ➔ {del.destination}

    +

    + {new Date(del.createdAt).toLocaleDateString()} at {new Date(del.createdAt).toLocaleTimeString()} +

    +
    +
    + + {del.status} + +

    Escrow: {del.escrowStatus}

    +

    ${del.amount}

    +
    +
  • Active Deliveries

    {data && data.length > 0 ? (
    @@ -174,6 +214,10 @@ export function DeliveryList() { ))}
    ) : ( +
    +

    No deliveries found.

    +

    Try adjusting your filters or creating a new delivery.

    +

    No deliveries found.

    )}
diff --git a/features/deliveries/components/DeliveryFilters.test.tsx b/features/deliveries/components/DeliveryFilters.test.tsx new file mode 100644 index 0000000..d818115 --- /dev/null +++ b/features/deliveries/components/DeliveryFilters.test.tsx @@ -0,0 +1,182 @@ +import React from 'react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { DeliveryFilters } from '../components/DeliveryFilters'; +import { useDeliveryFilters } from '../hooks/useDeliveryFilters'; + +// Mock the hook +jest.mock('../hooks/useDeliveryFilters', () => ({ + useDeliveryFilters: jest.fn(), +})); + +describe('DeliveryFilters Component', () => { + const mockUpdateFilters = jest.fn(); + const mockClearFilters = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + (useDeliveryFilters as jest.Mock).mockReturnValue({ + search: undefined, + status: undefined, + sortBy: undefined, + hasActiveFilters: false, + updateFilters: mockUpdateFilters, + clearFilters: mockClearFilters, + }); + }); + + it('should render filter component with all controls', () => { + render(); + + expect(screen.getByPlaceholderText('Search by Tracking ID...')).toBeInTheDocument(); + expect(screen.getByLabelText('Status')).toBeInTheDocument(); + expect(screen.getByLabelText('Sort by')).toBeInTheDocument(); + }); + + it('should update search on blur', async () => { + render(); + + const searchInput = screen.getByPlaceholderText('Search by Tracking ID...') as HTMLInputElement; + fireEvent.change(searchInput, { target: { value: 'TRK12345' } }); + fireEvent.blur(searchInput); + + await waitFor(() => { + expect(mockUpdateFilters).toHaveBeenCalledWith({ search: 'TRK12345' }); + }); + }); + + it('should update search on Enter key', async () => { + render(); + + const searchInput = screen.getByPlaceholderText('Search by Tracking ID...') as HTMLInputElement; + fireEvent.change(searchInput, { target: { value: 'TRK12345' } }); + fireEvent.keyDown(searchInput, { key: 'Enter' }); + + await waitFor(() => { + expect(mockUpdateFilters).toHaveBeenCalledWith({ search: 'TRK12345' }); + }); + }); + + it('should clear search when X button is clicked', async () => { + (useDeliveryFilters as jest.Mock).mockReturnValue({ + search: 'TRK12345', + status: undefined, + sortBy: undefined, + hasActiveFilters: true, + updateFilters: mockUpdateFilters, + clearFilters: mockClearFilters, + }); + + render(); + + const clearButton = screen.getByLabelText('Clear search'); + fireEvent.click(clearButton); + + await waitFor(() => { + expect(mockUpdateFilters).toHaveBeenCalledWith({ search: '' }); + }); + }); + + it('should handle status filter change', async () => { + render(); + + const statusSelect = screen.getByLabelText('Status') as HTMLSelectElement; + fireEvent.change(statusSelect, { target: { value: 'DELIVERED' } }); + + await waitFor(() => { + expect(mockUpdateFilters).toHaveBeenCalledWith({ status: 'DELIVERED' }); + }); + }); + + it('should handle sort filter change', async () => { + render(); + + const sortSelect = screen.getByLabelText('Sort by') as HTMLSelectElement; + fireEvent.change(sortSelect, { target: { value: 'date-desc' } }); + + await waitFor(() => { + expect(mockUpdateFilters).toHaveBeenCalledWith({ sortBy: 'date-desc' }); + }); + }); + + it('should display active filters when hasActiveFilters is true', () => { + (useDeliveryFilters as jest.Mock).mockReturnValue({ + search: 'TRK12345', + status: 'IN_TRANSIT', + sortBy: 'date-desc', + hasActiveFilters: true, + updateFilters: mockUpdateFilters, + clearFilters: mockClearFilters, + }); + + render(); + + expect(screen.getByText(/Active filters:/)).toBeInTheDocument(); + expect(screen.getByText(/Search: TRK12345/)).toBeInTheDocument(); + expect(screen.getByText(/Status: IN_TRANSIT/)).toBeInTheDocument(); + expect(screen.getByText(/Sort: Newest/)).toBeInTheDocument(); + expect(screen.getByText('Clear Filters')).toBeInTheDocument(); + }); + + it('should not display active filters section when no filters are active', () => { + (useDeliveryFilters as jest.Mock).mockReturnValue({ + search: undefined, + status: undefined, + sortBy: undefined, + hasActiveFilters: false, + updateFilters: mockUpdateFilters, + clearFilters: mockClearFilters, + }); + + render(); + + expect(screen.queryByText(/Active filters:/)).not.toBeInTheDocument(); + expect(screen.queryByText('Clear Filters')).not.toBeInTheDocument(); + }); + + it('should call clearFilters when Clear Filters button is clicked', async () => { + (useDeliveryFilters as jest.Mock).mockReturnValue({ + search: 'TRK12345', + status: 'DELIVERED', + sortBy: 'date-asc', + hasActiveFilters: true, + updateFilters: mockUpdateFilters, + clearFilters: mockClearFilters, + }); + + render(); + + const clearButton = screen.getByText('Clear Filters'); + fireEvent.click(clearButton); + + await waitFor(() => { + expect(mockClearFilters).toHaveBeenCalled(); + }); + }); + + it('should render all status options', () => { + render(); + + const statusSelect = screen.getByLabelText('Status'); + const options = statusSelect.querySelectorAll('option'); + + expect(options).toHaveLength(6); // All statuses + "All Statuses" + expect(options[0].textContent).toBe('All Statuses'); + expect(options[1].textContent).toBe('Pending'); + expect(options[2].textContent).toBe('Accepted'); + expect(options[3].textContent).toBe('In Transit'); + expect(options[4].textContent).toBe('Delivered'); + expect(options[5].textContent).toBe('Cancelled'); + }); + + it('should render all sort options', () => { + render(); + + const sortSelect = screen.getByLabelText('Sort by'); + const options = sortSelect.querySelectorAll('option'); + + expect(options).toHaveLength(3); + expect(options[0].textContent).toBe('No Sort'); + expect(options[1].textContent).toBe('Newest First'); + expect(options[2].textContent).toBe('Oldest First'); + }); +}); diff --git a/features/deliveries/components/DeliveryFilters.tsx b/features/deliveries/components/DeliveryFilters.tsx new file mode 100644 index 0000000..1d53dc1 --- /dev/null +++ b/features/deliveries/components/DeliveryFilters.tsx @@ -0,0 +1,163 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useDeliveryFilters } from '../hooks/useDeliveryFilters'; +import { Search, X } from 'lucide-react'; + +const STATUS_OPTIONS = [ + { value: '', label: 'All Statuses' }, + { value: 'PENDING', label: 'Pending' }, + { value: 'ACCEPTED', label: 'Accepted' }, + { value: 'IN_TRANSIT', label: 'In Transit' }, + { value: 'DELIVERED', label: 'Delivered' }, + { value: 'CANCELLED', label: 'Cancelled' }, +]; + +const SORT_OPTIONS = [ + { value: '', label: 'No Sort' }, + { value: 'date-desc', label: 'Newest First' }, + { value: 'date-asc', label: 'Oldest First' }, +]; + +export function DeliveryFilters() { + const { search, status, sortBy, hasActiveFilters, updateFilters, clearFilters } = useDeliveryFilters(); + const [localSearch, setLocalSearch] = useState(search || ''); + + // Handle search input changes + const handleSearchChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setLocalSearch(value); + // Debounce the API call by only updating on blur or submit + }, + [] + ); + + // Apply search on blur or Enter key + const handleSearchSubmit = useCallback(() => { + updateFilters({ search: localSearch }); + }, [localSearch, updateFilters]); + + const handleSearchKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearchSubmit(); + } + }, + [handleSearchSubmit] + ); + + // Clear search input + const handleClearSearch = useCallback(() => { + setLocalSearch(''); + updateFilters({ search: '' }); + }, [updateFilters]); + + // Handle status filter change + const handleStatusChange = useCallback( + (e: React.ChangeEvent) => { + updateFilters({ status: e.target.value }); + }, + [updateFilters] + ); + + // Handle sort change + const handleSortChange = useCallback( + (e: React.ChangeEvent) => { + updateFilters({ sortBy: e.target.value as 'date-asc' | 'date-desc' }); + }, + [updateFilters] + ); + + return ( +
+
+ {/* Search Bar */} +
+
+
+ +
+ + {localSearch && ( + + )} +
+
+ + {/* Filters Row */} +
+ {/* Status Filter */} +
+ + +
+ + {/* Sort Options */} +
+ + +
+
+ + {/* Active Filters Display & Clear Button */} + {hasActiveFilters && ( +
+
+ Active filters: + {search && Search: {search}} + {status && Status: {status}} + {sortBy && Sort: {sortBy === 'date-desc' ? 'Newest' : 'Oldest'}} +
+ +
+ )} +
+
+ ); +} diff --git a/features/deliveries/components/index.ts b/features/deliveries/components/index.ts new file mode 100644 index 0000000..0d537fe --- /dev/null +++ b/features/deliveries/components/index.ts @@ -0,0 +1 @@ +export { DeliveryFilters } from './DeliveryFilters'; diff --git a/features/deliveries/hooks/index.ts b/features/deliveries/hooks/index.ts new file mode 100644 index 0000000..55e81fb --- /dev/null +++ b/features/deliveries/hooks/index.ts @@ -0,0 +1 @@ +export { useDeliveryFilters } from './useDeliveryFilters'; diff --git a/features/deliveries/hooks/useDeliveryFilters.test.ts b/features/deliveries/hooks/useDeliveryFilters.test.ts new file mode 100644 index 0000000..83d8fd9 --- /dev/null +++ b/features/deliveries/hooks/useDeliveryFilters.test.ts @@ -0,0 +1,98 @@ +import { renderHook, act } from '@testing-library/react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import { useDeliveryFilters } from '../hooks/useDeliveryFilters'; + +// Mock next/navigation +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + useSearchParams: jest.fn(), +})); + +describe('useDeliveryFilters', () => { + const mockPush = jest.fn(); + const mockSearchParams = new URLSearchParams(); + + beforeEach(() => { + jest.clearAllMocks(); + (useRouter as jest.Mock).mockReturnValue({ + push: mockPush, + }); + (useSearchParams as jest.Mock).mockReturnValue(mockSearchParams); + }); + + it('should initialize with empty filters', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + expect(result.current.search).toBeUndefined(); + expect(result.current.status).toBeUndefined(); + expect(result.current.sortBy).toBeUndefined(); + expect(result.current.hasActiveFilters).toBe(false); + }); + + it('should update search filter', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + act(() => { + result.current.updateFilters({ search: 'TRK12345' }); + }); + + expect(mockPush).toHaveBeenCalledWith('?search=TRK12345', { scroll: false }); + }); + + it('should update status filter', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + act(() => { + result.current.updateFilters({ status: 'DELIVERED' }); + }); + + expect(mockPush).toHaveBeenCalledWith('?status=DELIVERED', { scroll: false }); + }); + + it('should update sort filter', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + act(() => { + result.current.updateFilters({ sortBy: 'date-desc' }); + }); + + expect(mockPush).toHaveBeenCalledWith('?sortBy=date-desc', { scroll: false }); + }); + + it('should combine multiple filters', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + act(() => { + result.current.updateFilters({ + search: 'TRK12345', + status: 'IN_TRANSIT', + sortBy: 'date-asc' + }); + }); + + const call = mockPush.mock.calls[0][0]; + expect(call).toContain('search=TRK12345'); + expect(call).toContain('status=IN_TRANSIT'); + expect(call).toContain('sortBy=date-asc'); + }); + + it('should clear all filters', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + act(() => { + result.current.clearFilters(); + }); + + expect(mockPush).toHaveBeenCalledWith('', { scroll: false }); + }); + + it('should remove a filter when set to empty string', () => { + const { result } = renderHook(() => useDeliveryFilters()); + + act(() => { + result.current.updateFilters({ search: '' }); + }); + + expect(mockPush).toHaveBeenCalledWith('', { scroll: false }); + }); +}); diff --git a/features/deliveries/hooks/useDeliveryFilters.ts b/features/deliveries/hooks/useDeliveryFilters.ts new file mode 100644 index 0000000..f68ad14 --- /dev/null +++ b/features/deliveries/hooks/useDeliveryFilters.ts @@ -0,0 +1,75 @@ +'use client'; + +import { useCallback, useMemo } from 'react'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { DeliveryFilterParams, FilterState } from '@/types/filters'; + +export function useDeliveryFilters() { + const searchParams = useSearchParams(); + const router = useRouter(); + + // Extract filter state from URL query params + const filterState = useMemo(() => { + const search = searchParams.get('search') || undefined; + const status = searchParams.get('status') || undefined; + const sortBy = (searchParams.get('sortBy') as 'date-asc' | 'date-desc') || undefined; + + const hasActiveFilters = !!(search || status || sortBy); + + return { + search, + status, + sortBy, + hasActiveFilters, + }; + }, [searchParams]); + + // Update URL query params + const updateFilters = useCallback( + (params: Partial) => { + const newParams = new URLSearchParams(searchParams); + + // Update or remove each parameter + if (params.search !== undefined) { + if (params.search) { + newParams.set('search', params.search); + } else { + newParams.delete('search'); + } + } + + if (params.status !== undefined) { + if (params.status) { + newParams.set('status', params.status); + } else { + newParams.delete('status'); + } + } + + if (params.sortBy !== undefined) { + if (params.sortBy) { + newParams.set('sortBy', params.sortBy); + } else { + newParams.delete('sortBy'); + } + } + + // Update router with new query params + const queryString = newParams.toString(); + const href = queryString ? `?${queryString}` : ''; + router.push(href, { scroll: false }); + }, + [searchParams, router] + ); + + // Clear all filters + const clearFilters = useCallback(() => { + router.push('', { scroll: false }); + }, [router]); + + return { + ...filterState, + updateFilters, + clearFilters, + }; +} diff --git a/hooks/useDeliveries.ts b/hooks/useDeliveries.ts index 07facd1..7a19913 100644 --- a/hooks/useDeliveries.ts +++ b/hooks/useDeliveries.ts @@ -1,11 +1,12 @@ import { useQuery } from '@tanstack/react-query'; import { deliveriesService } from '../services/deliveries.service'; import { Delivery } from '../types/delivery'; +import { DeliveryFilterParams } from '../types/filters'; -export function useDeliveries() { +export function useDeliveries(filters?: DeliveryFilterParams) { return useQuery({ - queryKey: ['deliveries'], - queryFn: deliveriesService.getDeliveries, + queryKey: ['deliveries', filters], + queryFn: () => deliveriesService.getDeliveries(filters), }); } diff --git a/package-lock.json b/package-lock.json index ee930bc..cb0d107 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,13 @@ "@hookform/resolvers": "^5.2.2", "@tailwindcss/postcss": "^4.2.4", "@tailwindcss/vite": "^4.2.4", - "@tanstack/react-query": "^5.0.0", + "@tanstack/react-table": "^8.0.0", "axios": "^1.6.0", "browser-image-compression": "^2.0.2", "clsx": "^2.0.0", "cmdk": "^1.1.1", "framer-motion": "^12.38.0", + "lodash.debounce": "^4.0.8", "lucide-react": "^1.9.0", "next": "16.1.6", "next-themes": "^0.4.6", @@ -3582,30 +3583,24 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, - "node_modules/@tanstack/query-core": { - "version": "5.100.5", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.5.tgz", - "integrity": "sha512-t20KrhKkf0HXzqQkPbJ5erhFesup68BAbwFgYmTrS7bxMF7O5MdmL8jUkik4thsG7Hg00fblz30h6yF1d5TxGg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.100.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.5.tgz", - "integrity": "sha512-aNwj1mi2v2bQ9IxkyR1grLOUkv3BYWoykHy9KDyLNbjC3tsahbOHJibK+Wjtr1wRhG59/AvJhiJG5OlthaCgJA==", + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.100.5" + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^18 || ^19" + "react": ">=16.8", + "react-dom": ">=16.8" } }, "node_modules/@tanstack/react-virtual": { @@ -3625,6 +3620,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-core": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.14.0.tgz", @@ -9490,6 +9498,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", diff --git a/services/__tests__/deliveries.service.test.ts b/services/__tests__/deliveries.service.test.ts new file mode 100644 index 0000000..c093bd5 --- /dev/null +++ b/services/__tests__/deliveries.service.test.ts @@ -0,0 +1,94 @@ +import { deliveriesService } from '../deliveries.service'; +import { apiClient } from '../api'; + +// Mock axios +jest.mock('../api', () => ({ + apiClient: { + get: jest.fn(), + }, +})); + +describe('deliveriesService', () => { + const mockDeliveries = [ + { + id: '1', + trackingNumber: 'TRK001', + status: 'DELIVERED', + origin: 'Nairobi', + destination: 'Mombasa', + amount: 100, + escrowStatus: 'RELEASED', + senderId: 'sender1', + createdAt: '2024-01-01T00:00:00Z', + updatedAt: '2024-01-01T00:00:00Z', + }, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch all deliveries without filters', async () => { + (apiClient.get as jest.Mock).mockResolvedValue({ data: mockDeliveries }); + + const result = await deliveriesService.getDeliveries(); + + expect(apiClient.get).toHaveBeenCalledWith('/deliveries'); + expect(result).toEqual(mockDeliveries); + }); + + it('should fetch deliveries with search filter', async () => { + (apiClient.get as jest.Mock).mockResolvedValue({ data: mockDeliveries }); + + await deliveriesService.getDeliveries({ search: 'TRK001' }); + + expect(apiClient.get).toHaveBeenCalledWith('/deliveries?search=TRK001'); + }); + + it('should fetch deliveries with status filter', async () => { + (apiClient.get as jest.Mock).mockResolvedValue({ data: mockDeliveries }); + + await deliveriesService.getDeliveries({ status: 'DELIVERED' }); + + expect(apiClient.get).toHaveBeenCalledWith('/deliveries?status=DELIVERED'); + }); + + it('should fetch deliveries with sortBy filter', async () => { + (apiClient.get as jest.Mock).mockResolvedValue({ data: mockDeliveries }); + + await deliveriesService.getDeliveries({ sortBy: 'date-desc' }); + + expect(apiClient.get).toHaveBeenCalledWith('/deliveries?sortBy=date-desc'); + }); + + it('should fetch deliveries with multiple filters', async () => { + (apiClient.get as jest.Mock).mockResolvedValue({ data: mockDeliveries }); + + await deliveriesService.getDeliveries({ + search: 'TRK001', + status: 'DELIVERED', + sortBy: 'date-asc', + }); + + const callUrl = (apiClient.get as jest.Mock).mock.calls[0][0]; + expect(callUrl).toContain('search=TRK001'); + expect(callUrl).toContain('status=DELIVERED'); + expect(callUrl).toContain('sortBy=date-asc'); + }); + + it('should fetch delivery by ID', async () => { + (apiClient.get as jest.Mock).mockResolvedValue({ data: mockDeliveries[0] }); + + const result = await deliveriesService.getDeliveryById('1'); + + expect(apiClient.get).toHaveBeenCalledWith('/deliveries/1'); + expect(result).toEqual(mockDeliveries[0]); + }); + + it('should handle API errors', async () => { + const error = new Error('Network error'); + (apiClient.get as jest.Mock).mockRejectedValue(error); + + await expect(deliveriesService.getDeliveries()).rejects.toThrow('Network error'); + }); +}); diff --git a/services/deliveries.service.ts b/services/deliveries.service.ts index e8080d4..859c18c 100644 --- a/services/deliveries.service.ts +++ b/services/deliveries.service.ts @@ -1,9 +1,28 @@ import { apiClient } from './api'; +import { Delivery } from '../types/delivery'; +import { DeliveryFilterParams } from '../types/filters'; import { Delivery, StatusTimeline } from '../types/delivery'; export const deliveriesService = { - getDeliveries: async (): Promise => { - const { data } = await apiClient.get('/deliveries'); + getDeliveries: async (filters?: DeliveryFilterParams): Promise => { + const params = new URLSearchParams(); + + if (filters?.search) { + params.append('search', filters.search); + } + + if (filters?.status) { + params.append('status', filters.status); + } + + if (filters?.sortBy) { + params.append('sortBy', filters.sortBy); + } + + const queryString = params.toString(); + const url = queryString ? `/deliveries?${queryString}` : '/deliveries'; + + const { data } = await apiClient.get(url); return data; }, diff --git a/services/escrowService.ts b/services/escrowService.ts index 34c33c5..cd63656 100644 --- a/services/escrowService.ts +++ b/services/escrowService.ts @@ -38,6 +38,9 @@ export interface LockEscrowParams { export interface LockEscrowResponse { success: boolean; message: string; + escrowId: string; + transactionHash: string; + lockedAmount: string; escrowId?: string; transactionHash?: string; lockedAmount?: string; @@ -106,6 +109,10 @@ export const escrowService = { return data; }, + async lockEscrow(params: LockEscrowParams): Promise { + const { data } = await axios.post( + `${API_BASE_URL}/api/escrow/lock`, + params async openDispute(params: OpenDisputeParams): Promise { const formData = new FormData(); formData.append('deliveryId', params.deliveryId); diff --git a/tailwind.config.ts b/tailwind.config.ts index 1f12457..559c13b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,3 +1,4 @@ +// @ts-ignore - Tailwind CSS v4 type resolution import type { Config } from 'tailwindcss'; const config: Config = { diff --git a/tsconfig.json b/tsconfig.json index b09cc80..fb87bad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es2020", "lib": [ "dom", "dom.iterable", @@ -8,6 +8,7 @@ ], "allowJs": true, "skipLibCheck": true, + "ignoreDeprecations": "6.0", "strict": true, "noEmit": true, "esModuleInterop": true, diff --git a/types/filters.ts b/types/filters.ts new file mode 100644 index 0000000..31cb3d5 --- /dev/null +++ b/types/filters.ts @@ -0,0 +1,9 @@ +export interface DeliveryFilterParams { + search?: string; + status?: string; + sortBy?: 'date-asc' | 'date-desc'; +} + +export interface FilterState extends DeliveryFilterParams { + hasActiveFilters: boolean; +}