diff --git a/UPGRADE_NEXT16.md b/UPGRADE_NEXT16.md new file mode 100644 index 0000000..46bf8d0 --- /dev/null +++ b/UPGRADE_NEXT16.md @@ -0,0 +1,79 @@ +# Next.js 16 Upgrade Guide + +## Current Status +- **Current Version**: Next.js 15.2.6 +- **Target Version**: Next.js 16.1.1 +- **React Version**: 19.0.0 ✅ (Compatible) + +## Upgrade Steps + +### 1. Backup First +```bash +git add . +git commit -m "Before Next.js 16 upgrade" +``` + +### 2. Update Dependencies +```bash +npm install next@16 react@19 react-dom@19 eslint-config-next@16 +``` + +### 3. Update Related Packages +```bash +npm install @next/bundle-analyzer@latest +``` + +### 4. Test Build +```bash +npm run build +``` + +### 5. Test Development Server +```bash +npm run dev +``` + +## Potential Breaking Changes + +### 1. Route Handlers +- Check all API routes in `app/api/` for compatibility +- Ensure async/await patterns are correct + +### 2. Server Components +- Verify all server components work correctly +- Check for any client component boundaries + +### 3. Image Optimization +- `next/image` may have minor API changes +- Check image imports + +### 4. Font Loading +- `next/font` should work the same, but verify + +## Performance Improvements Expected + +1. **Faster Builds**: 10-20% faster production builds +2. **Better Caching**: Improved static generation caching +3. **Smaller Bundles**: Better tree-shaking and code splitting +4. **Faster Dev Server**: Turbopack improvements + +## Rollback Plan + +If issues occur: +```bash +npm install next@15.2.6 react@19 react-dom@19 eslint-config-next@15.2.3 +``` + +## Testing Checklist + +- [ ] Build completes successfully +- [ ] Dev server starts without errors +- [ ] All pages load correctly +- [ ] API routes work +- [ ] Images load properly +- [ ] Fonts load correctly +- [ ] Admin dashboard works +- [ ] Order management works +- [ ] Analytics charts render +- [ ] Authentication works + diff --git a/app/dashboard/admin/vendors/page.tsx b/app/dashboard/admin/vendors/page.tsx index c2ab1a1..e864bbf 100644 --- a/app/dashboard/admin/vendors/page.tsx +++ b/app/dashboard/admin/vendors/page.tsx @@ -1,55 +1,83 @@ -import React from "react"; +"use client"; + +import React, { useState, useEffect, useCallback } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { Search, MoreHorizontal, UserCheck, UserX, Mail } from "lucide-react"; -import { fetchServer } from "@/lib/api"; +import { Search, MoreHorizontal, UserCheck, UserX, Mail, Loader2 } from "lucide-react"; +import { fetchClient } from "@/lib/api-client"; +import { useToast } from "@/hooks/use-toast"; interface Vendor { _id: string; username: string; - email?: string; storeId?: string; - pgpKey?: string; createdAt?: string; - currentToken?: string; + lastLogin?: string; isAdmin?: boolean; + isActive: boolean; } -export default async function AdminVendorsPage() { - let vendors: Vendor[] = []; - let error: string | null = null; +interface PaginationResponse { + success: boolean; + vendors: Vendor[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + hasNextPage: boolean; + hasPrevPage: boolean; + }; +} - try { - vendors = await fetchServer("/admin/vendors"); - } catch (err) { - console.error("Failed to fetch vendors:", err); - error = "Failed to load vendors"; - } +export default function AdminVendorsPage() { + const { toast } = useToast(); + const [loading, setLoading] = useState(true); + const [vendors, setVendors] = useState([]); + const [page, setPage] = useState(1); + const [pagination, setPagination] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); - if (error) { - return ( -
-
-

All Vendors

-

Manage vendor accounts and permissions

-
- - -
-

{error}

-
-
-
-
- ); - } + const fetchVendors = useCallback(async () => { + try { + setLoading(true); + const params = new URLSearchParams({ + page: page.toString(), + limit: '25' + }); + const data = await fetchClient(`/admin/vendors?${params.toString()}`); + setVendors(data.vendors); + setPagination(data.pagination); + } catch (error: any) { + console.error("Failed to fetch vendors:", error); + toast({ + title: "Error", + description: error.message || "Failed to load vendors", + variant: "destructive", + }); + } finally { + setLoading(false); + } + }, [page, toast]); - const activeVendors = vendors.filter(v => v.currentToken); + useEffect(() => { + fetchVendors(); + }, [fetchVendors]); + + const filteredVendors = searchQuery.trim() + ? vendors.filter(v => + v.username.toLowerCase().includes(searchQuery.toLowerCase()) || + (v.storeId && v.storeId.toString().toLowerCase().includes(searchQuery.toLowerCase())) + ) + : vendors; + + const activeVendors = vendors.filter(v => v.isActive); + const suspendedVendors = vendors.filter(v => !v.isActive); const adminVendors = vendors.filter(v => v.isAdmin); - const suspendedVendors = vendors.filter(v => !v.currentToken); + const totalVendors = pagination?.total || vendors.length; return (
@@ -65,7 +93,7 @@ export default async function AdminVendorsPage() { Total Vendors -
{vendors.length}
+
{totalVendors}

Registered vendors

@@ -113,7 +141,12 @@ export default async function AdminVendorsPage() {
- + setSearchQuery(e.target.value)} + />
- - - - Vendor - Store - Status - Join Date - Actions - - - - {vendors.map((vendor) => ( - - -
{vendor.username}
-
- {vendor.storeId || 'No store'} - -
- - {vendor.currentToken ? "active" : "suspended"} - - {vendor.isAdmin && ( - - Admin - - )} -
-
- - {vendor.createdAt ? new Date(vendor.createdAt).toLocaleDateString() : 'N/A'} - - -
- - - -
-
-
- ))} -
-
+ {loading ? ( +
+ +
+ ) : filteredVendors.length === 0 ? ( +
+ {searchQuery.trim() ? "No vendors found matching your search" : "No vendors found"} +
+ ) : ( + <> + + + + Vendor + Store + Status + Join Date + Last Login + Actions + + + + {filteredVendors.map((vendor) => ( + + +
{vendor.username}
+
+ {vendor.storeId || 'No store'} + +
+ + {vendor.isActive ? "active" : "suspended"} + + {vendor.isAdmin && ( + + Admin + + )} +
+
+ + {vendor.createdAt ? new Date(vendor.createdAt).toLocaleDateString() : 'N/A'} + + + {vendor.lastLogin ? new Date(vendor.lastLogin).toLocaleDateString() : 'Never'} + + +
+ + + +
+
+
+ ))} +
+
+ {pagination && pagination.totalPages > 1 && ( +
+ + Page {pagination.page} of {pagination.totalPages} ({pagination.total} total) + +
+ + +
+
+ )} + + )}
diff --git a/components/modals/broadcast-dialog.tsx b/components/modals/broadcast-dialog.tsx index fd08064..3226f23 100644 --- a/components/modals/broadcast-dialog.tsx +++ b/components/modals/broadcast-dialog.tsx @@ -1,15 +1,16 @@ "use client"; -import { useState, useRef } from "react"; +import { useState, useRef, lazy, Suspense } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Send, Bold, Italic, Code, Link as LinkIcon, Image as ImageIcon, X, Eye, EyeOff, Package } from "lucide-react"; import { toast } from "sonner"; import { clientFetch } from "@/lib/api"; import { Textarea } from "@/components/ui/textarea"; -import ReactMarkdown from 'react-markdown'; import ProductSelector from "./product-selector"; +const ReactMarkdown = lazy(() => import('react-markdown')); + interface BroadcastDialogProps { open: boolean; setOpen: (open: boolean) => void; @@ -265,7 +266,9 @@ __italic text__ {showPreview && broadcastMessage.trim() && (
- {broadcastMessage} + Loading preview...
}> + {broadcastMessage} +
)} diff --git a/public/git-info.json b/public/git-info.json index 95f0e66..7a65743 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "5f1e294", - "buildTime": "2025-12-31T06:44:25.736Z" + "commitHash": "18ac222", + "buildTime": "2025-12-31T06:46:19.353Z" } \ No newline at end of file