From 2f48ee38c249f7d46908b4409c561f9231984ad0 Mon Sep 17 00:00:00 2001 From: NotII <46204250+NotII@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:25:24 +0100 Subject: [PATCH] weewoo --- .gitignore | 1 + app/auth/register/page.tsx | 2 +- app/dashboard/categories/page.tsx | 2 +- app/dashboard/orders/[id]/page.tsx | 4 +- app/dashboard/page.tsx | 14 +- app/dashboard/products/page.tsx | 2 +- app/dashboard/shipping/page.tsx | 16 +- app/dashboard/stock/page.tsx | 2 +- app/dashboard/storefront/customers/page.tsx | 2 +- app/dashboard/storefront/page.tsx | 2 +- app/page.tsx | 4 +- components/KeepOnline.ts | 2 +- components/animated-stats-section.tsx | 2 +- components/dashboard/BuyerOrderInfo.tsx | 2 +- components/dashboard/ChatDetail.tsx | 4 +- components/dashboard/ChatNotifications.tsx | 2 +- components/dashboard/ChatTable.tsx | 20 +- components/dashboard/NewChatForm.tsx | 2 +- components/dashboard/content.tsx | 4 +- .../promotions/EditPromotionForm.tsx | 2 +- .../dashboard/promotions/NewPromotionForm.tsx | 2 +- .../dashboard/promotions/PromotionsList.tsx | 2 +- components/layout/sidebar.tsx | 2 +- components/modals/broadcast-dialog.tsx | 4 +- components/modals/product-modal.tsx | 2 +- .../notifications/OrderNotifications.tsx | 2 +- .../notifications/UnifiedNotifications.tsx | 4 +- components/tables/order-table.tsx | 2 +- components/ui/accordion.tsx | 2 +- components/ui/alert-dialog.tsx | 2 +- components/ui/alert.tsx | 2 +- components/ui/avatar.tsx | 2 +- components/ui/badge.tsx | 2 +- components/ui/breadcrumb.tsx | 2 +- components/ui/button.tsx | 2 +- components/ui/calendar.tsx | 2 +- components/ui/card.tsx | 2 +- components/ui/carousel.tsx | 2 +- components/ui/chart.tsx | 2 +- components/ui/checkbox.tsx | 2 +- components/ui/command.tsx | 2 +- components/ui/context-menu.tsx | 2 +- components/ui/dialog.tsx | 2 +- components/ui/drawer.tsx | 2 +- components/ui/dropdown-menu.tsx | 2 +- components/ui/form.tsx | 2 +- components/ui/input.tsx | 2 +- components/ui/label.tsx | 2 +- components/ui/menubar.tsx | 2 +- components/ui/navigation-menu.tsx | 2 +- components/ui/pagination.tsx | 2 +- components/ui/popover.tsx | 2 +- components/ui/progress.tsx | 2 +- components/ui/radio-group.tsx | 2 +- components/ui/resizable.tsx | 2 +- components/ui/scroll-area.tsx | 2 +- components/ui/select.tsx | 2 +- components/ui/separator.tsx | 2 +- components/ui/sheet.tsx | 2 +- components/ui/sidebar.tsx | 2 +- components/ui/skeleton.tsx | 2 +- components/ui/slider.tsx | 2 +- components/ui/switch.tsx | 2 +- components/ui/table.tsx | 2 +- components/ui/tabs.tsx | 2 +- components/ui/textarea.tsx | 2 +- components/ui/toast.tsx | 2 +- components/ui/toggle-group.tsx | 2 +- components/ui/toggle.tsx | 2 +- components/ui/tooltip.tsx | 2 +- lib/README.md | 82 ++++++ lib/api-client.ts | 264 +++++++++++++++++ lib/api-utils.ts | 71 ----- lib/api.ts | 136 +++++++++ lib/client-service.ts | 114 -------- lib/client-utils.ts | 93 ------ lib/data-service.ts | 13 - lib/git-utils.ts | 16 -- lib/productData.ts | 108 ------- lib/{server-service.ts => server-api.ts} | 66 ++++- lib/services/index.ts | 5 + lib/services/product-service.ts | 101 +++++++ lib/services/shipping-service.ts | 130 +++++++++ lib/services/stats-service.ts | 47 +++ lib/shippingHelper.ts | 116 -------- lib/stats-service.ts | 51 ---- lib/storeHelper.ts | 53 ---- lib/{types.ts => types/index.ts} | 1 + lib/{auth-utils.ts => utils/auth.ts} | 0 lib/{utils.ts => utils/general.ts} | 0 lib/utils/git.ts | 50 ++++ lib/utils/index.ts | 16 ++ lib/{ => utils}/styles.ts | 0 public/git-info.json | 4 +- scripts/cleanup-codebase.js | 80 ++++++ scripts/cleanup-deprecated-files.js | 159 +++++++++++ scripts/find-deprecated-imports.js | 89 ++++++ scripts/organize-lib-folder.js | 268 ++++++++++++++++++ scripts/remove-old-files.js | 102 +++++++ scripts/update-imports.js | 115 ++++++++ services/customerService.ts | 38 +-- services/index.ts | 3 + 102 files changed, 1825 insertions(+), 761 deletions(-) create mode 100644 lib/README.md create mode 100644 lib/api-client.ts delete mode 100644 lib/api-utils.ts create mode 100644 lib/api.ts delete mode 100644 lib/client-service.ts delete mode 100644 lib/client-utils.ts delete mode 100644 lib/data-service.ts delete mode 100644 lib/git-utils.ts delete mode 100644 lib/productData.ts rename lib/{server-service.ts => server-api.ts} (51%) create mode 100644 lib/services/index.ts create mode 100644 lib/services/product-service.ts create mode 100644 lib/services/shipping-service.ts create mode 100644 lib/services/stats-service.ts delete mode 100644 lib/shippingHelper.ts delete mode 100644 lib/stats-service.ts delete mode 100644 lib/storeHelper.ts rename lib/{types.ts => types/index.ts} (98%) rename lib/{auth-utils.ts => utils/auth.ts} (100%) rename lib/{utils.ts => utils/general.ts} (100%) create mode 100644 lib/utils/git.ts create mode 100644 lib/utils/index.ts rename lib/{ => utils}/styles.ts (100%) create mode 100644 scripts/cleanup-codebase.js create mode 100644 scripts/cleanup-deprecated-files.js create mode 100644 scripts/find-deprecated-imports.js create mode 100644 scripts/organize-lib-folder.js create mode 100644 scripts/remove-old-files.js create mode 100644 scripts/update-imports.js create mode 100644 services/index.ts diff --git a/.gitignore b/.gitignore index 51a3721..6a7850c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ next-env.d.ts *.zip .env env.local +/lib/deprecated-backup diff --git a/app/auth/register/page.tsx b/app/auth/register/page.tsx index f91389f..e61c5cd 100644 --- a/app/auth/register/page.tsx +++ b/app/auth/register/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { fetchData } from "@/lib/data-service"; +import { fetchData } from "@/lib/api"; import { useState } from "react"; import { useRouter } from "next/navigation"; import Image from "next/image"; diff --git a/app/dashboard/categories/page.tsx b/app/dashboard/categories/page.tsx index aba6ab1..8a84f8f 100644 --- a/app/dashboard/categories/page.tsx +++ b/app/dashboard/categories/page.tsx @@ -23,7 +23,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { apiRequest } from "@/lib/storeHelper"; +import { apiRequest } from "@/lib/api"; import type { Category } from "@/models/categories"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; diff --git a/app/dashboard/orders/[id]/page.tsx b/app/dashboard/orders/[id]/page.tsx index 15e160d..7df1b4f 100644 --- a/app/dashboard/orders/[id]/page.tsx +++ b/app/dashboard/orders/[id]/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { fetchData } from '@/lib/data-service'; -import { clientFetch } from '@/lib/client-utils'; +import { fetchData } from '@/lib/api'; +import { clientFetch } from '@/lib/api'; import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import { Button } from "@/components/ui/button"; diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 25e190b..1dcb69a 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,10 +1,10 @@ import Dashboard from "@/components/dashboard/dashboard"; import Content from "@/components/dashboard/content"; -import { fetchServer } from '@/lib/server-service'; +import { fetchServer } from '@/lib/api'; import { performance } from 'perf_hooks'; import { Info, GitCommit, User, Zap } from 'lucide-react'; import packageJson from '../../package.json'; -import { getGitCommitInfo } from '@/lib/git-utils'; +import { getGitInfo, getShortGitHash } from '@/lib/utils/git'; // ✅ Corrected Vendor Type interface Vendor { @@ -29,16 +29,18 @@ interface OrderStats { export default async function DashboardPage() { const startTime = performance.now(); - const [userResponse, orderStats, gitInfo] = await Promise.all([ + const [userResponse, orderStats] = await Promise.all([ fetchServer("/auth/me"), - fetchServer("/orders/stats"), - getGitCommitInfo() + fetchServer("/orders/stats") ]); + + // Get git info using the new utility + const gitInfo = getGitInfo(); const endTime = performance.now(); const generationTime = (endTime - startTime).toFixed(2); const panelVersion = packageJson.version; - const commitHash = gitInfo.commitHash; + const commitHash = gitInfo.hash; const vendor = userResponse.vendor; diff --git a/app/dashboard/products/page.tsx b/app/dashboard/products/page.tsx index b182348..f691c85 100644 --- a/app/dashboard/products/page.tsx +++ b/app/dashboard/products/page.tsx @@ -12,7 +12,7 @@ import { saveProductImage, deleteProductData, } from "@/lib/productData"; -import { clientFetch } from "@/lib/client-utils"; +import { clientFetch } from "@/lib/api"; import { ProductModal } from "@/components/modals/product-modal"; import ProductTable from "@/components/tables/product-table"; import { Category } from "@/models/categories"; diff --git a/app/dashboard/shipping/page.tsx b/app/dashboard/shipping/page.tsx index b09372d..5a71b06 100644 --- a/app/dashboard/shipping/page.tsx +++ b/app/dashboard/shipping/page.tsx @@ -12,9 +12,9 @@ import { addShippingMethod, deleteShippingMethod, updateShippingMethod, -} from "@/lib/shippingHelper"; - -import { ShippingMethod, ShippingData } from "@/lib/types"; + ShippingMethod, + ShippingData +} from "@/lib/services/shipping-service"; import { ShippingTable } from "@/components/tables/shipping-table"; @@ -89,8 +89,8 @@ export default function ShippingPage() { } await addShippingMethod( - authToken, - newShipping + newShipping, + authToken ); // Close modal and reset form before refreshing to avoid UI delays @@ -125,9 +125,9 @@ export default function ShippingPage() { } await updateShippingMethod( - authToken, newShipping._id, - newShipping + newShipping, + authToken ); // Close modal and reset form before refreshing to avoid UI delays @@ -150,7 +150,7 @@ export default function ShippingPage() { const handleDeleteShipping = async (_id: string) => { try { const authToken = document.cookie.split("Authorization=")[1]; - const response = await deleteShippingMethod(authToken, _id); + const response = await deleteShippingMethod(_id, authToken); if (response.success) { refreshShippingMethods(); // Refresh the list after deleting } else { diff --git a/app/dashboard/stock/page.tsx b/app/dashboard/stock/page.tsx index b56706c..6d277c5 100644 --- a/app/dashboard/stock/page.tsx +++ b/app/dashboard/stock/page.tsx @@ -26,7 +26,7 @@ import { } from "@/components/ui/alert-dialog"; import { Product } from "@/models/products"; import { Package, RefreshCw, ChevronDown, CheckSquare, XSquare } from "lucide-react"; -import { fetchProductData, updateProductStock, saveProductData } from "@/lib/productData"; +import { fetchProductData, updateProductStock, saveProductData } from "@/lib/api"; import { toast } from "sonner"; export default function StockManagementPage() { diff --git a/app/dashboard/storefront/customers/page.tsx b/app/dashboard/storefront/customers/page.tsx index 8b8a02c..7083fd0 100644 --- a/app/dashboard/storefront/customers/page.tsx +++ b/app/dashboard/storefront/customers/page.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useEffect, useState, useCallback } from "react"; -import { getCustomers, CustomerStats } from "@/services/customerService"; +import { getCustomers, type CustomerStats } from "@/lib/api"; import { formatCurrency } from "@/utils/format"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; diff --git a/app/dashboard/storefront/page.tsx b/app/dashboard/storefront/page.tsx index 4fda5ac..41c46c9 100644 --- a/app/dashboard/storefront/page.tsx +++ b/app/dashboard/storefront/page.tsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Save, Send, Key, MessageSquare, Shield, Globe, Wallet } from "lucide-react"; -import { apiRequest } from "@/lib/storeHelper"; +import { apiRequest } from "@/lib/api"; import { toast, Toaster } from "sonner"; import BroadcastDialog from "@/components/modals/broadcast-dialog"; import { diff --git a/app/page.tsx b/app/page.tsx index eb40aab..567fcc0 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { ArrowRight, Shield, LineChart, Zap } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { fetchPlatformStats } from "@/lib/stats-service"; +import { getPlatformStatsServer } from "@/lib/api"; import { HomeNavbar } from "@/components/home-navbar"; import { Suspense } from "react"; import { AnimatedStatsSection } from "@/components/animated-stats-section"; @@ -31,7 +31,7 @@ function formatCurrencyValue(amount: number): string { // This is a server component export default async function Home() { try { - const stats = await fetchPlatformStats(); + const stats = await getPlatformStatsServer(); return (
diff --git a/components/KeepOnline.ts b/components/KeepOnline.ts index 4989ad8..3770e20 100644 --- a/components/KeepOnline.ts +++ b/components/KeepOnline.ts @@ -1,7 +1,7 @@ "use client"; import { useEffect } from "react"; -import { clientFetch } from "@/lib/client-utils"; +import { clientFetch } from "@/lib/api"; const KeepOnline = () => { useEffect(() => { diff --git a/components/animated-stats-section.tsx b/components/animated-stats-section.tsx index 48c636c..68c3c12 100644 --- a/components/animated-stats-section.tsx +++ b/components/animated-stats-section.tsx @@ -2,7 +2,7 @@ import { Package, Users, CreditCard } from "lucide-react"; import { AnimatedCounter } from "./animated-counter"; -import { PlatformStats } from "@/lib/stats-service"; +import { PlatformStats } from "@/lib/api"; const formatCurrency = (value: number) => { return new Intl.NumberFormat('en-GB', { diff --git a/components/dashboard/BuyerOrderInfo.tsx b/components/dashboard/BuyerOrderInfo.tsx index af1c231..fc74d20 100644 --- a/components/dashboard/BuyerOrderInfo.tsx +++ b/components/dashboard/BuyerOrderInfo.tsx @@ -10,7 +10,7 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { getCookie } from "@/lib/client-utils"; +import { getCookie } from "@/lib/api"; import axios from "axios"; import { useRouter } from "next/navigation"; diff --git a/components/dashboard/ChatDetail.tsx b/components/dashboard/ChatDetail.tsx index 325634b..9b0374c 100644 --- a/components/dashboard/ChatDetail.tsx +++ b/components/dashboard/ChatDetail.tsx @@ -6,12 +6,12 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/general"; import { formatDistanceToNow } from "date-fns"; import axios from "axios"; import { toast } from "sonner"; import { ArrowLeft, Send, RefreshCw, File, FileText, Image as ImageIcon, Download } from "lucide-react"; -import { getCookie, clientFetch } from "@/lib/client-utils"; +import { getCookie, clientFetch } from "@/lib/api"; import { ImageViewerModal } from "@/components/modals/image-viewer-modal"; import BuyerOrderInfo from "./BuyerOrderInfo"; diff --git a/components/dashboard/ChatNotifications.tsx b/components/dashboard/ChatNotifications.tsx index f4763a9..fd672ef 100644 --- a/components/dashboard/ChatNotifications.tsx +++ b/components/dashboard/ChatNotifications.tsx @@ -11,7 +11,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { clientFetch } from "@/lib/client-utils"; +import { clientFetch } from "@/lib/api"; interface UnreadCounts { totalUnread: number; diff --git a/components/dashboard/ChatTable.tsx b/components/dashboard/ChatTable.tsx index 57f43e8..add4a76 100644 --- a/components/dashboard/ChatTable.tsx +++ b/components/dashboard/ChatTable.tsx @@ -1,7 +1,7 @@ "use client" -import { useState, useEffect, useRef } from "react"; -import { useRouter } from "next/navigation"; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; import { Table, TableBody, @@ -22,7 +22,15 @@ import { Eye, User, ChevronLeft, - ChevronRight + ChevronRight, + MessageSquare, + ArrowRightCircle, + X, + Clock, + CheckCheck, + Search, + Volume2, + VolumeX } from "lucide-react"; import { Select, @@ -31,9 +39,11 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { clientFetch } from "@/lib/client-utils"; import { toast } from "sonner"; -import { getCookie } from "@/lib/client-utils"; +import { clientFetch, getCookie } from "@/lib/api"; +import { formatDistance } from 'date-fns'; +import Link from 'next/link'; +import { cn } from '@/lib/utils/general'; interface Chat { _id: string; diff --git a/components/dashboard/NewChatForm.tsx b/components/dashboard/NewChatForm.tsx index d8fdb83..c8a7414 100644 --- a/components/dashboard/NewChatForm.tsx +++ b/components/dashboard/NewChatForm.tsx @@ -10,7 +10,7 @@ import { Textarea } from "@/components/ui/textarea"; import { ArrowLeft, Send, RefreshCw, Search, User } from "lucide-react"; import axios from "axios"; import { toast } from "sonner"; -import { getCookie } from "@/lib/client-utils"; +import { getCookie } from "@/lib/api"; import debounce from "lodash/debounce"; interface User { diff --git a/components/dashboard/content.tsx b/components/dashboard/content.tsx index 4c2bac8..b06ab8d 100644 --- a/components/dashboard/content.tsx +++ b/components/dashboard/content.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react" import OrderStats from "./order-stats" -import { getGreeting } from "@/lib/utils" +import { getGreeting } from "@/lib/utils/general" import { statsConfig } from "@/config/dashboard" import { getRandomQuote } from "@/config/quotes" import type { OrderStatsData } from "@/lib/types" @@ -11,7 +11,7 @@ import { ShoppingCart, RefreshCcw } from "lucide-react" import { Button } from "@/components/ui/button" import { useToast } from "@/components/ui/use-toast" import { Skeleton } from "@/components/ui/skeleton" -import { clientFetch } from "@/lib/client-utils" +import { clientFetch } from "@/lib/api" interface ContentProps { username: string diff --git a/components/dashboard/promotions/EditPromotionForm.tsx b/components/dashboard/promotions/EditPromotionForm.tsx index 01dbfa1..dd1c4d7 100644 --- a/components/dashboard/promotions/EditPromotionForm.tsx +++ b/components/dashboard/promotions/EditPromotionForm.tsx @@ -21,7 +21,7 @@ import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui/use-toast'; import { Promotion, PromotionFormData } from '@/lib/types/promotion'; -import { fetchClient } from '@/lib/client-service'; +import { fetchClient } from '@/lib/api'; // Form schema validation with Zod (same as NewPromotionForm) const formSchema = z.object({ diff --git a/components/dashboard/promotions/NewPromotionForm.tsx b/components/dashboard/promotions/NewPromotionForm.tsx index b1caa54..7060d52 100644 --- a/components/dashboard/promotions/NewPromotionForm.tsx +++ b/components/dashboard/promotions/NewPromotionForm.tsx @@ -21,7 +21,7 @@ import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui/use-toast'; import { PromotionFormData } from '@/lib/types/promotion'; -import { fetchClient } from '@/lib/client-service'; +import { fetchClient } from '@/lib/api'; // Form schema validation with Zod const formSchema = z.object({ diff --git a/components/dashboard/promotions/PromotionsList.tsx b/components/dashboard/promotions/PromotionsList.tsx index 05338bc..63e7f12 100644 --- a/components/dashboard/promotions/PromotionsList.tsx +++ b/components/dashboard/promotions/PromotionsList.tsx @@ -31,7 +31,7 @@ import { import { toast } from '@/components/ui/use-toast'; import { Badge } from '@/components/ui/badge'; import { Promotion } from '@/lib/types/promotion'; -import { fetchClient } from '@/lib/client-service'; +import { fetchClient } from '@/lib/api'; import NewPromotionForm from './NewPromotionForm'; import EditPromotionForm from './EditPromotionForm'; diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx index f7cb6bd..b02f22b 100644 --- a/components/layout/sidebar.tsx +++ b/components/layout/sidebar.tsx @@ -7,7 +7,7 @@ import { ShoppingCart, LogOut } from "lucide-react" import { NavItem } from "./nav-item" import { Button } from "@/components/ui/button" import { sidebarConfig } from "@/config/sidebar" -import { logoutUser } from "@/lib/auth-utils" +import { logoutUser } from "@/lib/utils/auth" import { toast } from "sonner" const Sidebar: React.FC = () => { diff --git a/components/modals/broadcast-dialog.tsx b/components/modals/broadcast-dialog.tsx index 3a7435d..870b1dd 100644 --- a/components/modals/broadcast-dialog.tsx +++ b/components/modals/broadcast-dialog.tsx @@ -5,8 +5,8 @@ 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 } from "lucide-react"; import { toast } from "sonner"; -import { apiRequest } from "@/lib/storeHelper"; -import { cn } from "@/lib/utils"; +import { apiRequest } from "@/lib/api"; +import { cn } from "@/lib/utils/general"; import { Textarea } from "@/components/ui/textarea"; import ReactMarkdown from 'react-markdown'; diff --git a/components/modals/product-modal.tsx b/components/modals/product-modal.tsx index b506965..37e39b0 100644 --- a/components/modals/product-modal.tsx +++ b/components/modals/product-modal.tsx @@ -20,7 +20,7 @@ import type { ProductModalProps, ProductData } from "@/lib/types"; import { toast } from "sonner"; import type React from "react"; import { Plus } from "lucide-react"; -import { apiRequest } from "@/lib/storeHelper"; +import { apiRequest } from "@/lib/api"; type CategorySelectProps = { categories: { _id: string; name: string; parentId?: string }[]; diff --git a/components/notifications/OrderNotifications.tsx b/components/notifications/OrderNotifications.tsx index fc533c3..64c3022 100644 --- a/components/notifications/OrderNotifications.tsx +++ b/components/notifications/OrderNotifications.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState, useRef } from "react"; -import { clientFetch } from "@/lib/client-utils"; +import { clientFetch } from "@/lib/api"; import { toast } from "sonner"; import { Package, Bell } from "lucide-react"; import { useRouter } from "next/navigation"; diff --git a/components/notifications/UnifiedNotifications.tsx b/components/notifications/UnifiedNotifications.tsx index c1d0eca..421b960 100644 --- a/components/notifications/UnifiedNotifications.tsx +++ b/components/notifications/UnifiedNotifications.tsx @@ -13,9 +13,9 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { clientFetch } from "@/lib/client-utils"; +import { clientFetch } from "@/lib/api"; import { toast } from "sonner"; -import { getCookie } from "@/lib/client-utils"; +import { getCookie } from "@/lib/api"; import axios from "axios"; interface Order { diff --git a/components/tables/order-table.tsx b/components/tables/order-table.tsx index 9ab5518..9ca1aea 100644 --- a/components/tables/order-table.tsx +++ b/components/tables/order-table.tsx @@ -30,7 +30,7 @@ import { MessageCircle } from "lucide-react"; import Link from "next/link"; -import { clientFetch } from '@/lib/client-utils'; +import { clientFetch } from '@/lib/api'; import { toast } from "sonner"; import { Checkbox } from "@/components/ui/checkbox"; import { diff --git a/components/ui/accordion.tsx b/components/ui/accordion.tsx index f5b49e4..070c70f 100644 --- a/components/ui/accordion.tsx +++ b/components/ui/accordion.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as AccordionPrimitive from "@radix-ui/react-accordion" import { ChevronDown } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Accordion = AccordionPrimitive.Root diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx index bb747d1..81f54b8 100644 --- a/components/ui/alert-dialog.tsx +++ b/components/ui/alert-dialog.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { buttonVariants } from "@/components/ui/button" const AlertDialog = AlertDialogPrimitive.Root diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx index a8c94c8..06df060 100644 --- a/components/ui/alert.tsx +++ b/components/ui/alert.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const alertVariants = cva( "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx index e47fd7d..72d04dd 100644 --- a/components/ui/avatar.tsx +++ b/components/ui/avatar.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as AvatarPrimitive from "@radix-ui/react-avatar" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Avatar = React.forwardRef< React.ElementRef, diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx index 8f51932..480294f 100644 --- a/components/ui/badge.tsx +++ b/components/ui/badge.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", diff --git a/components/ui/breadcrumb.tsx b/components/ui/breadcrumb.tsx index 6ff0e4a..532f09c 100644 --- a/components/ui/breadcrumb.tsx +++ b/components/ui/breadcrumb.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { ChevronRight, MoreHorizontal } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Breadcrumb = React.forwardRef< HTMLElement, diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 5c63761..25790d0 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", diff --git a/components/ui/calendar.tsx b/components/ui/calendar.tsx index 6a1ccf9..990feca 100644 --- a/components/ui/calendar.tsx +++ b/components/ui/calendar.tsx @@ -4,7 +4,7 @@ import * as React from "react" import { ChevronLeft, ChevronRight } from "lucide-react" import { DayPicker } from "react-day-picker" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { buttonVariants } from "@/components/ui/button" export type CalendarProps = React.ComponentProps diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 0906be5..b39c5a6 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -1,6 +1,6 @@ import * as React from "react" -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils/general" const Card = React.forwardRef>(({ className, ...props }, ref) => (
diff --git a/components/ui/carousel.tsx b/components/ui/carousel.tsx index 75ced14..3bb8c21 100644 --- a/components/ui/carousel.tsx +++ b/components/ui/carousel.tsx @@ -6,7 +6,7 @@ import useEmblaCarousel, { } from "embla-carousel-react" import { ArrowLeft, ArrowRight } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { Button } from "@/components/ui/button" type CarouselApi = UseEmblaCarouselType[1] diff --git a/components/ui/chart.tsx b/components/ui/chart.tsx index 1b1ff89..d621d11 100644 --- a/components/ui/chart.tsx +++ b/components/ui/chart.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as RechartsPrimitive from "recharts" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx index 8d8d005..b0171aa 100644 --- a/components/ui/checkbox.tsx +++ b/components/ui/checkbox.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Checkbox = React.forwardRef< React.ElementRef, diff --git a/components/ui/command.tsx b/components/ui/command.tsx index 745fba7..73d036c 100644 --- a/components/ui/command.tsx +++ b/components/ui/command.tsx @@ -5,7 +5,7 @@ import { type DialogProps } from "@radix-ui/react-dialog" import { Command as CommandPrimitive } from "cmdk" import { Search } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { Dialog, DialogContent } from "@/components/ui/dialog" const Command = React.forwardRef< diff --git a/components/ui/context-menu.tsx b/components/ui/context-menu.tsx index 3eee764..125f11d 100644 --- a/components/ui/context-menu.tsx +++ b/components/ui/context-menu.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import { Check, ChevronRight, Circle } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const ContextMenu = ContextMenuPrimitive.Root diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index 4233e40..442fb65 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Dialog = DialogPrimitive.Root diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx index d678328..e91bd4b 100644 --- a/components/ui/drawer.tsx +++ b/components/ui/drawer.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { Drawer as DrawerPrimitive } from "vaul" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Drawer = ({ shouldScaleBackground = true, diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx index 61cd595..b295e2a 100644 --- a/components/ui/dropdown-menu.tsx +++ b/components/ui/dropdown-menu.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const DropdownMenu = DropdownMenuPrimitive.Root diff --git a/components/ui/form.tsx b/components/ui/form.tsx index 975f29d..7b8d7e7 100644 --- a/components/ui/form.tsx +++ b/components/ui/form.tsx @@ -12,7 +12,7 @@ import { useFormContext, } from "react-hook-form" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { Label } from "@/components/ui/label" const Form = FormProvider diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 9dbdb50..19dc1da 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -1,6 +1,6 @@ import * as React from "react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Input = React.forwardRef>( ({ className, type, ...props }, ref) => { diff --git a/components/ui/label.tsx b/components/ui/label.tsx index ccc561d..f61b3bf 100644 --- a/components/ui/label.tsx +++ b/components/ui/label.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const labelVariants = cva( "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" diff --git a/components/ui/menubar.tsx b/components/ui/menubar.tsx index 1052dc2..1e067c8 100644 --- a/components/ui/menubar.tsx +++ b/components/ui/menubar.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as MenubarPrimitive from "@radix-ui/react-menubar" import { Check, ChevronRight, Circle } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const MenubarMenu = MenubarPrimitive.Menu diff --git a/components/ui/navigation-menu.tsx b/components/ui/navigation-menu.tsx index d0ff4ac..c084a57 100644 --- a/components/ui/navigation-menu.tsx +++ b/components/ui/navigation-menu.tsx @@ -3,7 +3,7 @@ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" import { cva } from "class-variance-authority" import { ChevronDown } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const NavigationMenu = React.forwardRef< React.ElementRef, diff --git a/components/ui/pagination.tsx b/components/ui/pagination.tsx index eed0b30..9ad1c59 100644 --- a/components/ui/pagination.tsx +++ b/components/ui/pagination.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { ButtonProps, buttonVariants } from "@/components/ui/button" const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx index 97b3c09..d128d6e 100644 --- a/components/ui/popover.tsx +++ b/components/ui/popover.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Popover = PopoverPrimitive.Root diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx index 76d388b..0d401fd 100644 --- a/components/ui/progress.tsx +++ b/components/ui/progress.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as ProgressPrimitive from "@radix-ui/react-progress" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Progress = React.forwardRef< React.ElementRef, diff --git a/components/ui/radio-group.tsx b/components/ui/radio-group.tsx index 638ab40..9726319 100644 --- a/components/ui/radio-group.tsx +++ b/components/ui/radio-group.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import { Circle } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const RadioGroup = React.forwardRef< React.ElementRef, diff --git a/components/ui/resizable.tsx b/components/ui/resizable.tsx index 4c4323d..d77f153 100644 --- a/components/ui/resizable.tsx +++ b/components/ui/resizable.tsx @@ -3,7 +3,7 @@ import { GripVertical } from "lucide-react" import * as ResizablePrimitive from "react-resizable-panels" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const ResizablePanelGroup = ({ className, diff --git a/components/ui/scroll-area.tsx b/components/ui/scroll-area.tsx index 666de88..e625243 100644 --- a/components/ui/scroll-area.tsx +++ b/components/ui/scroll-area.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const ScrollArea = React.forwardRef< React.ElementRef, diff --git a/components/ui/select.tsx b/components/ui/select.tsx index e232114..13bca40 100644 --- a/components/ui/select.tsx +++ b/components/ui/select.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as SelectPrimitive from "@radix-ui/react-select" import { Check, ChevronDown, ChevronUp } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Select = SelectPrimitive.Root diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx index ec48df4..b9930d9 100644 --- a/components/ui/separator.tsx +++ b/components/ui/separator.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Separator = React.forwardRef< React.ElementRef, diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx index f00ad45..77f40f8 100644 --- a/components/ui/sheet.tsx +++ b/components/ui/sheet.tsx @@ -5,7 +5,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Sheet = SheetPrimitive.Root diff --git a/components/ui/sidebar.tsx b/components/ui/sidebar.tsx index 91eaad6..bcea5c8 100644 --- a/components/ui/sidebar.tsx +++ b/components/ui/sidebar.tsx @@ -6,7 +6,7 @@ import { VariantProps, cva } from "class-variance-authority" import { PanelLeft } from "lucide-react" import { useIsMobile } from "@/hooks/use-mobile" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" diff --git a/components/ui/skeleton.tsx b/components/ui/skeleton.tsx index 6795fc9..3e2b7d0 100644 --- a/components/ui/skeleton.tsx +++ b/components/ui/skeleton.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; function Skeleton({ className, diff --git a/components/ui/slider.tsx b/components/ui/slider.tsx index ecba997..f941aca 100644 --- a/components/ui/slider.tsx +++ b/components/ui/slider.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as SliderPrimitive from "@radix-ui/react-slider" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Slider = React.forwardRef< React.ElementRef, diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx index 9e253a3..cb9279b 100644 --- a/components/ui/switch.tsx +++ b/components/ui/switch.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as SwitchPrimitives from "@radix-ui/react-switch" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Switch = React.forwardRef< React.ElementRef, diff --git a/components/ui/table.tsx b/components/ui/table.tsx index aab59fc..b83bda8 100644 --- a/components/ui/table.tsx +++ b/components/ui/table.tsx @@ -1,6 +1,6 @@ import * as React from "react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Table = React.forwardRef< HTMLTableElement, diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx index 6f62573..ddf91f9 100644 --- a/components/ui/tabs.tsx +++ b/components/ui/tabs.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as TabsPrimitive from "@radix-ui/react-tabs" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Tabs = TabsPrimitive.Root diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx index 374b8ac..89d213a 100644 --- a/components/ui/textarea.tsx +++ b/components/ui/textarea.tsx @@ -1,6 +1,6 @@ import * as React from "react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const Textarea = React.forwardRef< HTMLTextAreaElement, diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx index ec64bfc..2d960fa 100644 --- a/components/ui/toast.tsx +++ b/components/ui/toast.tsx @@ -5,7 +5,7 @@ import * as ToastPrimitives from "@radix-ui/react-toast" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const ToastProvider = ToastPrimitives.Provider diff --git a/components/ui/toggle-group.tsx b/components/ui/toggle-group.tsx index df1b9e5..e16c3dc 100644 --- a/components/ui/toggle-group.tsx +++ b/components/ui/toggle-group.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" import { type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; import { toggleVariants } from "@/components/ui/toggle" const ToggleGroupContext = React.createContext< diff --git a/components/ui/toggle.tsx b/components/ui/toggle.tsx index 2b28c1f..9f76738 100644 --- a/components/ui/toggle.tsx +++ b/components/ui/toggle.tsx @@ -4,7 +4,7 @@ import * as React from "react" import * as TogglePrimitive from "@radix-ui/react-toggle" import { cva, type VariantProps } from "class-variance-authority" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const toggleVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2", diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx index 60c9bf9..0c10cda 100644 --- a/components/ui/tooltip.tsx +++ b/components/ui/tooltip.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as TooltipPrimitive from "@radix-ui/react-tooltip" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils/styles"; const TooltipProvider = TooltipPrimitive.Provider diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..717029a --- /dev/null +++ b/lib/README.md @@ -0,0 +1,82 @@ +# API & Utilities Organization + +This directory contains the API client and utility functions used throughout the application. + +## Directory Structure + +``` +lib/ +├─ api.ts # Main API entry point +├─ api-client.ts # Client-side API functions +├─ server-api.ts # Server-side API functions +├─ services/ # Service modules +│ ├─ index.ts # Service re-exports +│ ├─ product-service.ts # Product API +│ ├─ shipping-service.ts # Shipping API +│ └─ stats-service.ts # Statistics API +├─ utils/ # Utility functions +│ └─ index.ts # Utility re-exports +├─ types.ts # Common TypeScript types +├─ utils.ts # General utilities +├─ auth-utils.ts # Authentication utilities +└─ styles.ts # Styling utilities +``` + +## API Structure + +- `api.ts` - The main API entry point that re-exports all API functionality +- `api-client.ts` - Client-side API functions and types +- `server-api.ts` - Server-side API functions (for server components in the app/ directory) + +## How to Use + +### In Client Components + +```typescript +// Import what you need from the main API module +import { clientFetch, getCustomers, getProducts } from '@/lib/api'; + +// Example usage +const customers = await getCustomers(1, 25); +const products = await getProducts(1, 10); +``` + +### In Server Components (app/ directory only) + +```typescript +// Server functions only work in Server Components in the app/ directory +import { getCustomersServer } from '@/lib/api'; + +// In a Server Component +export default async function Page() { + const customers = await getCustomersServer(1, 25); + // ... +} +``` + +## Server Components Compatibility + +The server-side API functions (`fetchServer`, `getCustomersServer`, etc.) can **only** be used in Server Components within the app/ directory. They will throw an error if used in: + +- Client Components +- The pages/ directory +- Any code that runs in the browser + +This is because they rely on Next.js server-only features like the `cookies()` function from `next/headers`. + +## Utilities + +For utilities, you can either import specific functions or use the utils index: + +```typescript +// Import specific utilities +import { cn } from '@/lib/utils'; +import { getAuthToken } from '@/lib/auth-utils'; + +// Or import from the utils index +import { cn, getAuthToken } from '@/lib/utils'; +``` + +## Backward Compatibility + +For backward compatibility, many functions are also re-exported from their original locations. \ No newline at end of file diff --git a/lib/api-client.ts b/lib/api-client.ts new file mode 100644 index 0000000..4de125e --- /dev/null +++ b/lib/api-client.ts @@ -0,0 +1,264 @@ +'use client'; + +// Import toast conditionally to prevent build errors +let toast: any; +try { + // Try to import from the UI components + toast = require("@/components/ui/use-toast").toast; +} catch (error) { + // Fallback toast function if not available + toast = { + error: (message: string) => console.error(message) + }; +} + +// Types +type FetchMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + +interface FetchOptions { + method?: FetchMethod; + body?: any; + cache?: RequestCache; + headers?: HeadersInit; +} + +// Customer types +export interface CustomerStats { + userId: string; + telegramUserId: number; + telegramUsername: string; + totalOrders: number; + totalSpent: number; + ordersByStatus: { + paid: number; + completed: number; + acknowledged: number; + shipped: number; + }; + lastOrderDate: string | null; + firstOrderDate: string; + chatId: number; + hasOrders?: boolean; +} + +export interface CustomerResponse { + customers: CustomerStats[]; + total: number; + success?: boolean; +} + +/** + * Normalizes a URL to ensure it has the correct /api prefix + * This prevents double prefixing which causes API errors + */ +function normalizeApiUrl(url: string): string { + // Remove any existing /api or api prefix + const cleanPath = url.replace(/^\/?(api\/)+/, ''); + + // Add a single /api prefix + return `/api/${cleanPath.replace(/^\//, '')}`; +} + +/** + * Get the authentication token from cookies or localStorage + */ +export function getAuthToken(): string | null { + if (typeof document === 'undefined') return null; // Guard for SSR + + return document.cookie + .split('; ') + .find(row => row.startsWith('Authorization=')) + ?.split('=')[1] || localStorage.getItem('Authorization'); +} + +/** + * Get a cookie value by name + */ +export function getCookie(name: string): string | undefined { + if (typeof document === 'undefined') return undefined; // Guard for SSR + + return document.cookie + .split('; ') + .find(row => row.startsWith(`${name}=`)) + ?.split('=')[1]; +} + +/** + * Creates standard API request headers with authentication + * + * @param token Optional auth token (fetched automatically if not provided) + * @param customHeaders Additional headers to include + * @returns Headers object ready for fetch requests + */ +function createApiHeaders(token?: string | null, customHeaders: Record = {}): Headers { + const headers = new Headers({ + 'Content-Type': 'application/json', + 'accept': '*/*', + ...customHeaders + }); + + const authToken = token || getAuthToken(); + if (authToken) { + headers.set('authorization', `Bearer ${authToken}`); + } + + return headers; +} + +/** + * Simple client-side fetch function for making API calls with Authorization header. + * Uses the Next.js API proxy to make requests through the same domain. + */ +export async function clientFetch(url: string, options: RequestInit = {}): Promise { + try { + // Create headers with authentication + const headers = createApiHeaders(null, options.headers as Record); + + // Normalize URL to ensure it uses the Next.js API proxy + const fullUrl = normalizeApiUrl(url); + + const res = await fetch(fullUrl, { + ...options, + headers, + credentials: 'include', + mode: 'cors', + referrerPolicy: 'strict-origin-when-cross-origin' + }); + + if (!res.ok) { + const errorData = await res.json().catch(() => ({})); + const errorMessage = errorData.message || errorData.error || `Request failed: ${res.status} ${res.statusText}`; + throw new Error(errorMessage); + } + + // Handle 204 No Content responses + if (res.status === 204) { + return {} as T; + } + + return await res.json(); + } catch (error) { + console.error(`Client fetch error at ${url}:`, error); + throw error; + } +} + +/** + * Enhanced client-side fetch function with error toast notifications. + * Use this when you want automatic error handling with toast notifications. + */ +export async function fetchClient( + endpoint: string, + options: FetchOptions = {} +): Promise { + const { method = 'GET', body, headers = {}, ...rest } = options; + + // Get the base API URL from environment or fallback + const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; + + // Ensure the endpoint starts with a slash + const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + + // For the specific case of internal-api.inboxi.ng - remove duplicate /api + let url; + if (apiUrl.includes('internal-api.inboxi.ng')) { + // Special case for internal-api.inboxi.ng + if (normalizedEndpoint.startsWith('/api/')) { + url = `${apiUrl}${normalizedEndpoint.substring(4)}`; // Remove the /api part + } else { + url = `${apiUrl}${normalizedEndpoint}`; + } + } else { + // Normal case for other environments + url = `${apiUrl}${normalizedEndpoint}`; + } + + // Get auth token from cookies + const authToken = getAuthToken(); + + // Prepare headers with authentication if token exists + const requestHeaders: Record = { + 'Content-Type': 'application/json', + ...(headers as Record), + }; + + if (authToken) { + // Backend expects "Bearer TOKEN" format + requestHeaders['Authorization'] = `Bearer ${authToken}`; + } + + const fetchOptions: RequestInit = { + method, + credentials: 'include', + headers: requestHeaders, + ...rest, + }; + + if (body && method !== 'GET') { + fetchOptions.body = JSON.stringify(body); + } + + try { + const response = await fetch(url, fetchOptions); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + const errorMessage = errorData.message || errorData.error || 'An error occurred'; + + throw new Error(errorMessage); + } + + if (response.status === 204) { + return {} as T; + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('API request failed:', error); + + // Only show toast if this is a client-side error (not during SSR) + if (typeof window !== 'undefined') { + const message = error instanceof Error ? error.message : 'Failed to connect to server'; + + // Handle different toast implementations + if (toast?.title && toast?.description) { + // shadcn/ui toast format + toast({ + title: 'Error', + description: message, + variant: 'destructive', + }); + } else if (typeof toast?.error === 'function') { + // sonner or other simple toast + toast.error(message); + } else { + // Fallback to console + console.error('API error:', message); + } + } + + throw error; + } +} + +// =========== API SERVICES =========== + +/** + * Get a paginated list of customers + * @param page Page number (starting from 1) + * @param limit Number of items per page + * @returns Promise with customers data and total count + */ +export const getCustomers = async (page: number = 1, limit: number = 25): Promise => { + return clientFetch(`/customers?page=${page}&limit=${limit}`); +}; + +/** + * Get detailed stats for a specific customer + * @param userId The customer's user ID + * @returns Promise with detailed customer stats + */ +export const getCustomerDetails = async (userId: string): Promise => { + return clientFetch(`/customers/${userId}`); +}; \ No newline at end of file diff --git a/lib/api-utils.ts b/lib/api-utils.ts deleted file mode 100644 index 6801bcf..0000000 --- a/lib/api-utils.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * API utilities for client and server-side requests - */ - -/** - * Normalizes the API URL to ensure it uses the proper prefix - * For client-side, ensures all requests go through the Next.js API proxy - */ -export function normalizeApiUrl(url: string): string { - // If URL already starts with http or https, return as is - if (url.startsWith('http://') || url.startsWith('https://')) { - return url; - } - - // If URL already starts with /api, use as is - if (url.startsWith('/api/')) { - return url; - } - - // Otherwise, ensure it has the /api prefix - return `/api${url.startsWith('/') ? '' : '/'}${url}`; -} - -/** - * Get the server API URL for server-side requests - */ -export function getServerApiUrl(endpoint: string): string { - // Get the base API URL from environment - const baseUrl = process.env.SERVER_API_URL || process.env.NEXT_PUBLIC_API_URL || 'https://internal-api.inboxi.ng/api'; - - // Ensure it doesn't have trailing slash - const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; - - // Ensure endpoint has leading slash - const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; - - return `${normalizedBaseUrl}${normalizedEndpoint}`; -} - -/** - * Get the authentication token from cookies or localStorage - * Only available in client-side code - */ -export function getAuthToken(): string | null { - if (typeof document === 'undefined') return null; - - return document.cookie - .split('; ') - .find(row => row.startsWith('Authorization=')) - ?.split('=')[1] || - (typeof localStorage !== 'undefined' ? localStorage.getItem('Authorization') : null); -} - -/** - * Create headers with authentication for API requests - */ -export function createApiHeaders(token: string | null = null, additionalHeaders: Record = {}): Headers { - const headers = new Headers({ - 'Content-Type': 'application/json', - ...additionalHeaders - }); - - // Use provided token or try to get it from storage - const authToken = token || getAuthToken(); - - if (authToken) { - headers.append('Authorization', `Bearer ${authToken}`); - } - - return headers; -} \ No newline at end of file diff --git a/lib/api.ts b/lib/api.ts new file mode 100644 index 0000000..fe26d61 --- /dev/null +++ b/lib/api.ts @@ -0,0 +1,136 @@ +// Re-export client API functions +export { + // Core client API functions + clientFetch, + fetchClient, + getAuthToken, + getCookie, + + // Customer API + getCustomers, + getCustomerDetails, + + // Types + type CustomerStats, + type CustomerResponse, +} from './api-client'; + +// Re-export product services +export { + getProducts, + getProductDetails, + createProduct, + updateProduct, + deleteProduct, + uploadProductImage, + getProductStock, + updateProductStock, + + // Types + type Product, + type ProductsResponse, + type StockData, +} from './services/product-service'; + +// Re-export shipping services +export { + getShippingOptions, + getShippingOption, + createShippingOption, + updateShippingOption, + deleteShippingOption, + + // Types + type ShippingOption, + type ShippingOptionsResponse, +} from './services/shipping-service'; + +// Re-export stats services +export { + getPlatformStats, + getVendorStats, + + // Types + type PlatformStats, +} from './services/stats-service'; + +// Get clientFetch first so we can use it in the compatibility functions +import { clientFetch } from './api-client'; +import { getProductDetails, updateProduct } from './services/product-service'; +import { getPlatformStats } from './services/stats-service'; + +// Add missing functions for backward compatibility +// These are functions from the old style that we need to maintain compatibility +export const fetchData = async (endpoint: string, options: any = {}) => { + console.warn('fetchData is deprecated, use clientFetch instead'); + return clientFetch(endpoint, options); +}; + +export const apiRequest = async (endpoint: string, method = 'GET', data: any = null, token: string | null = null) => { + console.warn('apiRequest is deprecated, use clientFetch instead'); + const options: RequestInit & { headers: Record } = { + method, + headers: { 'Content-Type': 'application/json' }, + body: data ? JSON.stringify(data) : undefined, + }; + + if (token) { + options.headers.Authorization = `Bearer ${token}`; + } + + return clientFetch(endpoint, options); +}; + +// Product-specific compatibility functions +export const fetchProductData = async (productId: string, token?: string) => { + console.warn('fetchProductData is deprecated, use getProductDetails instead'); + return getProductDetails(productId); +}; + +export const saveProductData = async (productData: any, productId: string, token?: string) => { + console.warn('saveProductData is deprecated, use updateProduct instead'); + return updateProduct(productId, productData); +}; + +// Stats compatibility function +export const fetchPlatformStats = async () => { + console.warn('fetchPlatformStats is deprecated, use getPlatformStats instead'); + return getPlatformStats(); +}; + +// Server API functions are conditionally exported +// They are only usable in Server Components in the app/ directory +// Dynamically check if we can use server components +let canUseServerComponents = false; +try { + // Check if next/headers is available + require('next/headers'); + canUseServerComponents = true; +} catch (e) { + // We're not in a Server Component context + // This is normal in Client Components and pages/ directory +} + +// Handle server API functions +// Define function types first for TypeScript +type ServerFetchFn = (url: string, options?: RequestInit) => Promise; +type CustomerServerFn = (options?: any) => Promise; +type CustomerDetailServerFn = (id: string, options?: any) => Promise; +type PlatformStatsServerFn = () => Promise; + +// Export the functions for use in server components +export const fetchServer: ServerFetchFn = canUseServerComponents + ? require('./server-api').fetchServer + : (() => { throw new Error('fetchServer can only be used in Server Components'); }) as any; + +export const getCustomersServer: CustomerServerFn = canUseServerComponents + ? require('./server-api').getCustomersServer + : (() => { throw new Error('getCustomersServer can only be used in Server Components'); }) as any; + +export const getCustomerDetailsServer: CustomerDetailServerFn = canUseServerComponents + ? require('./server-api').getCustomerDetailsServer + : (() => { throw new Error('getCustomerDetailsServer can only be used in Server Components'); }) as any; + +export const getPlatformStatsServer: PlatformStatsServerFn = canUseServerComponents + ? require('./server-api').getPlatformStatsServer + : (() => { throw new Error('getPlatformStatsServer can only be used in Server Components'); }) as any; \ No newline at end of file diff --git a/lib/client-service.ts b/lib/client-service.ts deleted file mode 100644 index b6a2ebe..0000000 --- a/lib/client-service.ts +++ /dev/null @@ -1,114 +0,0 @@ -'use client'; - -import { toast } from "@/components/ui/use-toast"; - -type FetchMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; - -interface FetchOptions { - method?: FetchMethod; - body?: any; - cache?: RequestCache; - headers?: HeadersInit; -} - -// Helper function to get auth token from cookies -function getAuthToken(): string | null { - if (typeof document === 'undefined') return null; // Guard for SSR - - return document.cookie - .split('; ') - .find(row => row.startsWith('Authorization=')) - ?.split('=')[1] || null; -} - -export async function fetchClient( - endpoint: string, - options: FetchOptions = {} -): Promise { - const { method = 'GET', body, headers = {}, ...rest } = options; - - // Get the base API URL from environment or fallback - const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; - - // Ensure the endpoint starts with a slash - const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; - - // For the specific case of internal-api.inboxi.ng - remove duplicate /api - let url; - if (apiUrl.includes('internal-api.inboxi.ng')) { - // Special case for internal-api.inboxi.ng - if (normalizedEndpoint.startsWith('/api/')) { - url = `${apiUrl}${normalizedEndpoint.substring(4)}`; // Remove the /api part - } else { - url = `${apiUrl}${normalizedEndpoint}`; - } - } else { - // Normal case for other environments - url = `${apiUrl}${normalizedEndpoint}`; - } - - // Get auth token from cookies - const authToken = getAuthToken(); - - // Prepare headers with authentication if token exists - const requestHeaders: Record = { - 'Content-Type': 'application/json', - ...(headers as Record), - }; - - if (authToken) { - // Backend expects "Bearer TOKEN" format - requestHeaders['Authorization'] = `Bearer ${authToken}`; - console.log('Authorization header set to:', `Bearer ${authToken.substring(0, 10)}...`); - } - - console.log('API Request:', { - url, - method, - hasAuthToken: !!authToken - }); - - const fetchOptions: RequestInit = { - method, - credentials: 'include', - headers: requestHeaders, - ...rest, - }; - - if (body && method !== 'GET') { - fetchOptions.body = JSON.stringify(body); - } - - try { - const response = await fetch(url, fetchOptions); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - const errorMessage = errorData.message || errorData.error || 'An error occurred'; - - throw new Error(errorMessage); - } - - if (response.status === 204) { - return {} as T; - } - - const data = await response.json(); - return data; - } catch (error) { - console.error('API request failed:', error); - - // Only show toast if this is a client-side error (not during SSR) - if (typeof window !== 'undefined') { - const message = error instanceof Error ? error.message : 'Failed to connect to server'; - - toast({ - title: 'Error', - description: message, - variant: 'destructive', - }); - } - - throw error; - } -} \ No newline at end of file diff --git a/lib/client-utils.ts b/lib/client-utils.ts deleted file mode 100644 index 340ef5d..0000000 --- a/lib/client-utils.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Normalizes a URL to ensure it has the correct /api prefix - * This prevents double prefixing which causes API errors - */ -function normalizeApiUrl(url: string): string { - // Remove any existing /api or api prefix - const cleanPath = url.replace(/^\/?(api\/)+/, ''); - - // Add a single /api prefix - return `/api/${cleanPath.replace(/^\//, '')}`; -} - -/** - * Get the authentication token from cookies or localStorage - */ -function getAuthToken(): string | null { - if (typeof document === 'undefined') return null; // Guard for SSR - - return document.cookie - .split('; ') - .find(row => row.startsWith('Authorization=')) - ?.split('=')[1] || localStorage.getItem('Authorization'); -} - -/** - * Creates standard API request headers with authentication - * - * @param token Optional auth token (fetched automatically if not provided) - * @param customHeaders Additional headers to include - * @returns Headers object ready for fetch requests - */ -function createApiHeaders(token?: string | null, customHeaders: Record = {}): Headers { - const headers = new Headers({ - 'Content-Type': 'application/json', - 'accept': '*/*', - ...customHeaders - }); - - const authToken = token || getAuthToken(); - if (authToken) { - headers.set('authorization', `Bearer ${authToken}`); - } - - return headers; -} - -/** - * Simple client-side fetch function for making API calls with Authorization header. - * Ensures all requests go through the Next.js API proxy. - */ -export async function clientFetch(url: string, options: RequestInit = {}): Promise { - try { - // Create headers with authentication - const headers = createApiHeaders(null, options.headers as Record); - - // Normalize URL to ensure it uses the Next.js API proxy - const fullUrl = normalizeApiUrl(url); - - const res = await fetch(fullUrl, { - ...options, - headers, - credentials: 'include', - mode: 'cors', - referrerPolicy: 'strict-origin-when-cross-origin' - }); - - if (!res.ok) { - const errorData = await res.json().catch(() => ({})); - const errorMessage = errorData.message || errorData.error || `Request failed: ${res.status} ${res.statusText}`; - throw new Error(errorMessage); - } - - // Handle 204 No Content responses - if (res.status === 204) { - return {} as T; - } - - return await res.json(); - } catch (error) { - console.error(`Client fetch error at ${url}:`, error); - throw error; - } -} - -/** - * Get a cookie value by name - */ -export function getCookie(name: string): string | undefined { - return document.cookie - .split('; ') - .find(row => row.startsWith(`${name}=`)) - ?.split('=')[1]; -} \ No newline at end of file diff --git a/lib/data-service.ts b/lib/data-service.ts deleted file mode 100644 index 04aa20b..0000000 --- a/lib/data-service.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Client-side fetch function for API requests. - */ -export async function fetchData(url: string, options: RequestInit = {}): Promise { - try { - const res = await fetch(url, options); - if (!res.ok) throw new Error(`Request failed: ${res.statusText}`); - return res.json(); - } catch (error) { - console.error(`Fetch error at ${url}:`, error); - throw error; - } -} \ No newline at end of file diff --git a/lib/git-utils.ts b/lib/git-utils.ts deleted file mode 100644 index cf2a2a7..0000000 --- a/lib/git-utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import gitInfo from "../public/git-info.json"; - -/** - * Git utility to load commit hash in both development and production environments - */ - -interface GitInfo { - commitHash: string; - buildTime: string; -} - -let cachedGitInfo: GitInfo | null = null; - -export async function getGitCommitInfo(): Promise { - return gitInfo; -} \ No newline at end of file diff --git a/lib/productData.ts b/lib/productData.ts deleted file mode 100644 index 1576440..0000000 --- a/lib/productData.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { fetchData } from '@/lib/data-service'; - -export const fetchProductData = async (url: string, authToken: string) => { - try { - return await fetchData(url, { - headers: { Authorization: `Bearer ${authToken}` }, - credentials: "include", - }); - } catch (error) { - console.error("Error fetching product data:", error); - throw error; - } -}; - -export const saveProductData = async ( - url: string, - data: any, - authToken: string, - method: "POST" | "PUT" = "POST" -) => { - try { - return await fetchData(url, { - method, - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - }); - } catch (error) { - console.error("Error saving product data:", error); - throw error; - } -}; - -export const saveProductImage = async(url: string, file:File, authToken: string) => { - try{ - const formData = new FormData(); - formData.append("file", file); - - return await fetchData(url, { - method: "PUT", - headers: { - Authorization: `Bearer ${authToken}`, - }, - body: formData, - }); - } catch (error) { - console.error("Error uploading image:", error); - throw error; - } -} - -export const deleteProductData = async (url: string, authToken: string) => { - try { - return await fetchData(url, { - method: "DELETE", - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - }); - } catch (error) { - console.error("Error deleting product data:", error); - throw error; - } -}; - -// Stock management functions -export const fetchStockData = async (url: string, authToken: string) => { - try { - return await fetchData(url, { - headers: { Authorization: `Bearer ${authToken}` }, - credentials: "include", - }); - } catch (error) { - console.error("Error fetching stock data:", error); - throw error; - } -}; - -export const updateProductStock = async ( - productId: string, - stockData: { - currentStock: number; - stockTracking?: boolean; - lowStockThreshold?: number; - }, - authToken: string -) => { - try { - const url = `${process.env.NEXT_PUBLIC_API_URL}/stock/${productId}`; - return await fetchData(url, { - method: "PUT", - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(stockData), - }); - } catch (error) { - console.error("Error updating product stock:", error); - throw error; - } -}; \ No newline at end of file diff --git a/lib/server-service.ts b/lib/server-api.ts similarity index 51% rename from lib/server-service.ts rename to lib/server-api.ts index 3fdfe2f..85a0fba 100644 --- a/lib/server-service.ts +++ b/lib/server-api.ts @@ -1,5 +1,17 @@ -import { cookies } from 'next/headers'; +// This module is only meant to be used in Server Components in the app/ directory +// It cannot be imported in Client Components or pages/ directory + import { redirect } from 'next/navigation'; +import { CustomerResponse, CustomerStats } from './api-client'; + +// Dynamically import cookies to prevent build errors +let cookiesModule: any; +try { + // This will only work in server components + cookiesModule = require('next/headers'); +} catch (error) { + console.warn('Warning: next/headers only works in Server Components in the app/ directory'); +} /** * Constructs a server-side API URL for backend requests @@ -21,13 +33,23 @@ function getServerApiUrl(endpoint: string): string { * Server-side fetch wrapper with authentication. * Used in Server Components to make authenticated API requests to the backend. * This uses the SERVER_API_URL environment variable and is different from client-side fetching. + * + * @throws Error if used outside of a Server Component in the app/ directory */ export async function fetchServer( endpoint: string, options: RequestInit = {} ): Promise { + // Check if we're in a server component context + if (!cookiesModule?.cookies) { + throw new Error( + "fetchServer can only be used in Server Components in the app/ directory. " + + "For client components, use clientFetch or fetchClient instead." + ); + } + // Get auth token from cookies - const cookieStore = await cookies(); + const cookieStore = cookiesModule.cookies(); const authToken = cookieStore.get('Authorization')?.value; // Redirect to login if not authenticated @@ -68,4 +90,42 @@ export async function fetchServer( console.error(`Server request to ${endpoint} failed:`, error); throw error; } -} \ No newline at end of file +} + +// =========== SERVER API SERVICES =========== + +/** + * Get a paginated list of customers (server-side) + * @param page Page number (starting from 1) + * @param limit Number of items per page + * @returns Promise with customers data and total count + */ +export const getCustomersServer = async (page: number = 1, limit: number = 25): Promise => { + return fetchServer(`/customers?page=${page}&limit=${limit}`); +}; + +/** + * Get detailed stats for a specific customer (server-side) + * @param userId The customer's user ID + * @returns Promise with detailed customer stats + */ +export const getCustomerDetailsServer = async (userId: string): Promise => { + return fetchServer(`/customers/${userId}`); +}; + +// Server-side platform stats function +export async function getPlatformStatsServer(): Promise { + try { + return fetchServer('/stats/platform'); + } catch (error) { + console.error('Error fetching platform stats (server):', error); + // Return default stats to prevent UI breakage + return { + totalProducts: 0, + totalVendors: 0, + totalOrders: 0, + totalCustomers: 0, + gmv: 0 + }; + } +} \ No newline at end of file diff --git a/lib/services/index.ts b/lib/services/index.ts new file mode 100644 index 0000000..da3dbb0 --- /dev/null +++ b/lib/services/index.ts @@ -0,0 +1,5 @@ +// Re-export all service functionality +export * from './product-service'; +export * from './shipping-service'; +export * from './stats-service'; +export * from '../api-client'; \ No newline at end of file diff --git a/lib/services/product-service.ts b/lib/services/product-service.ts new file mode 100644 index 0000000..21db135 --- /dev/null +++ b/lib/services/product-service.ts @@ -0,0 +1,101 @@ +import { clientFetch } from '../api-client'; + +// Product data types +export interface Product { + _id: string; + name: string; + description: string; + price: number; + imageUrl?: string; + category?: string; + stock?: number; + status: 'active' | 'inactive'; +} + +export interface ProductsResponse { + products: Product[]; + total: number; + success?: boolean; +} + +export interface StockData { + currentStock: number; + stockTracking?: boolean; + lowStockThreshold?: number; +} + +/** + * Get all products with pagination + */ +export const getProducts = async (page: number = 1, limit: number = 25): Promise => { + return clientFetch(`/products?page=${page}&limit=${limit}`); +}; + +/** + * Get a specific product by ID + */ +export const getProductDetails = async (productId: string): Promise => { + return clientFetch(`/products/${productId}`); +}; + +/** + * Create a new product + */ +export const createProduct = async (productData: Omit): Promise => { + return clientFetch('/products', { + method: 'POST', + body: JSON.stringify(productData), + }); +}; + +/** + * Update a product + */ +export const updateProduct = async (productId: string, productData: Partial): Promise => { + return clientFetch(`/products/${productId}`, { + method: 'PUT', + body: JSON.stringify(productData), + }); +}; + +/** + * Delete a product + */ +export const deleteProduct = async (productId: string): Promise<{ success: boolean }> => { + return clientFetch(`/products/${productId}`, { + method: 'DELETE', + }); +}; + +/** + * Upload a product image + */ +export const uploadProductImage = async (productId: string, file: File): Promise<{ imageUrl: string }> => { + const formData = new FormData(); + formData.append('file', file); + + return clientFetch(`/products/${productId}/image`, { + method: 'PUT', + body: formData, + headers: { + // Don't set Content-Type when sending FormData, the browser will set it with the boundary + } + }); +}; + +/** + * Get product stock information + */ +export const getProductStock = async (productId: string): Promise => { + return clientFetch(`/stock/${productId}`); +}; + +/** + * Update product stock + */ +export const updateProductStock = async (productId: string, stockData: StockData): Promise => { + return clientFetch(`/stock/${productId}`, { + method: 'PUT', + body: JSON.stringify(stockData), + }); +}; \ No newline at end of file diff --git a/lib/services/shipping-service.ts b/lib/services/shipping-service.ts new file mode 100644 index 0000000..092c3e6 --- /dev/null +++ b/lib/services/shipping-service.ts @@ -0,0 +1,130 @@ +import { clientFetch } from '../api-client'; + +/** + * Shipping service - Handles shipping options + * Replaces the old shippingHelper.ts + */ + +export interface ShippingOption { + _id?: string; + name: string; + price: number; + createdAt?: string; + updatedAt?: string; +} + +export interface ShippingOptionsResponse { + success: boolean; + data: ShippingOption[]; +} + +// Get all shipping options +export const getShippingOptions = async (authToken?: string): Promise => { + console.log('Fetching shipping options'); + + const options: RequestInit = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }; + + if (authToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${authToken}`, + }; + } + + const response = await clientFetch('/api/shipping', options); + return response.data || []; +}; + +// Get a single shipping option +export const getShippingOption = async (id: string, authToken?: string): Promise => { + const options: RequestInit = { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }; + + if (authToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${authToken}`, + }; + } + + const response = await clientFetch<{success: boolean, data: ShippingOption}>(`/api/shipping/${id}`, options); + return response.data; +}; + +// Create a new shipping option +export const createShippingOption = async (data: ShippingOption, authToken?: string) => { + const options: RequestInit = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }; + + if (authToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${authToken}`, + }; + } + + return clientFetch<{success: boolean, data: ShippingOption}>('/api/shipping', options); +}; + +// Update a shipping option +export const updateShippingOption = async (id: string, data: ShippingOption, authToken?: string) => { + const options: RequestInit = { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }; + + if (authToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${authToken}`, + }; + } + + return clientFetch<{success: boolean, data: ShippingOption}>(`/api/shipping/${id}`, options); +}; + +// Delete a shipping option +export const deleteShippingOption = async (id: string, authToken?: string) => { + const options: RequestInit = { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + }; + + if (authToken) { + options.headers = { + ...options.headers, + 'Authorization': `Bearer ${authToken}`, + }; + } + + return clientFetch<{success: boolean}>(`/api/shipping/${id}`, options); +}; + +// Compatibility with old shippingHelper functions +export const fetchShippingMethods = getShippingOptions; +export const addShippingMethod = createShippingOption; +export const updateShippingMethod = updateShippingOption; +export const deleteShippingMethod = deleteShippingOption; + +// Types for backward compatibility +export type ShippingMethod = ShippingOption; +export type ShippingData = ShippingOption; \ No newline at end of file diff --git a/lib/services/stats-service.ts b/lib/services/stats-service.ts new file mode 100644 index 0000000..110cafd --- /dev/null +++ b/lib/services/stats-service.ts @@ -0,0 +1,47 @@ +import { clientFetch } from '../api-client'; + +// Stats data types +export interface PlatformStats { + orders: { + completed: number; + }; + vendors: { + total: number; + }; + transactions: { + volume: number; + averageOrderValue: number; + }; +} + +/** + * Get platform statistics + */ +export const getPlatformStats = async (): Promise => { + try { + return await clientFetch('/stats/platform'); + } catch (error) { + console.error('Error fetching platform stats:', error); + + // Return fallback data if API fails + return { + orders: { + completed: 15800 + }, + vendors: { + total: 2400 + }, + transactions: { + volume: 3200000, + averageOrderValue: 220 + } + }; + } +}; + +/** + * Get vendor-specific statistics + */ +export const getVendorStats = async (): Promise => { + return clientFetch('/stats/vendor'); +}; \ No newline at end of file diff --git a/lib/shippingHelper.ts b/lib/shippingHelper.ts deleted file mode 100644 index 9c9463b..0000000 --- a/lib/shippingHelper.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { fetchData } from '@/lib/data-service'; - -export const fetchShippingMethods = async (authToken: string) => { - try { - const res = await fetchData( - `${process.env.NEXT_PUBLIC_API_URL}/shipping-options`, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - } - ); - - console.log("Shipping Methods Response:", res); - - if (!res) throw new Error("Failed to fetch shipping options"); - return res - } catch (error) { - console.error("Error loading shipping options:", error); - throw error; - } -}; - -interface ShippingMethod { - name: string; - price: number; - _id?: string; -} - -export const addShippingMethod = async ( - authToken: string, - newShipping: Omit -): Promise => { - try { - const res = await fetchData( - `${process.env.NEXT_PUBLIC_API_URL}/shipping-options`, - { - method: "POST", - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(newShipping), - } - ); - - // If fetchData returns directly (not a Response object), just return it - if (!res.ok && !res.status) { - return res; - } - - // Handle if it's a Response object - if (!res.ok) { - const errorData = await res.json(); - throw new Error(errorData.message || "Failed to add shipping method"); - } - - return await res.json(); - } catch (error) { - console.error("Error adding shipping method:", error); - throw new Error( - error instanceof Error - ? error.message - : "An unexpected error occurred while adding shipping method" - ); - } -}; - -export const deleteShippingMethod = async (authToken: string, id: string) => { - try { - const res = await fetchData( - `${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`, - { - method: "DELETE", - headers: { Authorization: `Bearer ${authToken}` }, - credentials: "include", - } - ); - - if (!res.ok) throw new Error("Failed to delete shipping method"); - return { success: res.status === 204 }; - } catch (error) { - console.error("Error deleting shipping method:", error); - throw error; - } -}; - -export const updateShippingMethod = async ( - authToken: string, - id: string, - updatedShipping: any -) => { - try { - const res = await fetchData( - `${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`, - { - method: "PUT", - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(updatedShipping), - } - ); - - if (!res) throw new Error("Failed to update shipping method"); - return res; - } catch (error) { - console.error("Error updating shipping method:", error); - throw error; - } -}; \ No newline at end of file diff --git a/lib/stats-service.ts b/lib/stats-service.ts deleted file mode 100644 index 0ebf82d..0000000 --- a/lib/stats-service.ts +++ /dev/null @@ -1,51 +0,0 @@ -export interface PlatformStats { - orders: { - completed: number; - }; - vendors: { - total: number; - }; - transactions: { - volume: number; - averageOrderValue: number; - }; -} - -export async function fetchPlatformStats(): Promise { - const BASE_API_URL = process.env.SERVER_API_URL || 'http://localhost:3001/api'; - - console.log('Fetching platform stats from:', BASE_API_URL); - - try { - const response = await fetch(`${BASE_API_URL}/stats/platform`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - cache: 'no-store' - }); - - if (!response.ok) { - throw new Error(`API error: ${response.status}`); - } - - const data = await response.json(); - console.log('Fetched stats:', data); - return data; - } catch (error) { - console.error('Error fetching platform stats:', error); - // Return fallback data if API fails - return { - orders: { - completed: 15800 - }, - vendors: { - total: 2400 - }, - transactions: { - volume: 3200000, - averageOrderValue: 220 - } - }; - } -} \ No newline at end of file diff --git a/lib/storeHelper.ts b/lib/storeHelper.ts deleted file mode 100644 index ddb4167..0000000 --- a/lib/storeHelper.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { fetchData } from '@/lib/data-service'; - -export const apiRequest = async (endpoint: string, method: string = "GET", body?: T | null) => { - try { - if (typeof document === "undefined") { - throw new Error("API requests must be made from the client side."); - } - - const authToken = document.cookie - .split("; ") - .find((row) => row.startsWith("Authorization=")) - ?.split("=")[1]; - - if (!authToken){ - document.location.href = "/login"; - throw new Error("No authentication token found"); - } - - const options: RequestInit = { - method, - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - credentials: "include", - }; - - if (body) { - options.body = JSON.stringify(body); - } - - const API_URL = process.env.NEXT_PUBLIC_API_URL; - if (!API_URL) throw new Error("NEXT_PUBLIC_API_URL is not set in environment variables"); - - const res = await fetchData(`${API_URL}${endpoint}`, options); - - if (!res) { - const errorResponse = await res.json().catch(() => null); - const errorMessage = errorResponse?.error || res.statusText || "Unknown error"; - throw new Error(`Failed to ${method} ${endpoint}: ${errorMessage}`); - } - - return res; - } catch (error: unknown) { - if (error instanceof Error) { - console.error(`🚨 API Request Error: ${error.message}`); - throw new Error(error.message); - } - - console.error("❌ An unknown error occurred", error); - throw new Error("An unknown error occurred"); - } -}; \ No newline at end of file diff --git a/lib/types.ts b/lib/types/index.ts similarity index 98% rename from lib/types.ts rename to lib/types/index.ts index b9f8f3f..0bf0b4a 100644 --- a/lib/types.ts +++ b/lib/types/index.ts @@ -59,6 +59,7 @@ export interface PricingTier { export interface Category { _id: string name: string + parentId?: string } export interface OrderStatsData { diff --git a/lib/auth-utils.ts b/lib/utils/auth.ts similarity index 100% rename from lib/auth-utils.ts rename to lib/utils/auth.ts diff --git a/lib/utils.ts b/lib/utils/general.ts similarity index 100% rename from lib/utils.ts rename to lib/utils/general.ts diff --git a/lib/utils/git.ts b/lib/utils/git.ts new file mode 100644 index 0000000..fb7765a --- /dev/null +++ b/lib/utils/git.ts @@ -0,0 +1,50 @@ +// Git utility functions + +interface GitInfo { + hash: string; + date: string; + message: string; +} + +// Default git info if file is not found +const defaultGitInfo: GitInfo = { + hash: 'local-dev', + date: new Date().toISOString(), + message: 'Local development build', +}; + +/** + * Get git info - safely handles cases where the JSON file isn't available + */ +export function getGitInfo(): GitInfo { + try { + // In production builds, the git info should be available + // In development, we'll use default values + const gitInfo = { + hash: process.env.NEXT_PUBLIC_GIT_HASH || 'dev', + date: process.env.NEXT_PUBLIC_GIT_DATE || new Date().toISOString(), + message: process.env.NEXT_PUBLIC_GIT_MESSAGE || 'Development build', + }; + + return gitInfo; + } catch (error) { + console.warn('Could not load git info, using defaults', error); + return defaultGitInfo; + } +} + +/** + * Get a shorter git hash for display + */ +export function getShortGitHash(): string { + const { hash } = getGitInfo(); + return hash.substring(0, 7); +} + +/** + * Format git commit date for display + */ +export function getFormattedGitDate(): string { + const { date } = getGitInfo(); + return new Date(date).toLocaleDateString(); +} \ No newline at end of file diff --git a/lib/utils/index.ts b/lib/utils/index.ts new file mode 100644 index 0000000..0812fa3 --- /dev/null +++ b/lib/utils/index.ts @@ -0,0 +1,16 @@ +/** + * Main utilities index file + * Re-exports all utility functions from their respective modules + */ + +// Re-export general utils +export * from './general'; + +// Re-export auth utils +export * from './auth'; + +// Re-export git utils +export * from './git'; + +// Re-export style utils +export * from './styles'; diff --git a/lib/styles.ts b/lib/utils/styles.ts similarity index 100% rename from lib/styles.ts rename to lib/utils/styles.ts diff --git a/public/git-info.json b/public/git-info.json index ec72774..c181c53 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "8da4d00", - "buildTime": "2025-04-06T15:28:50.532Z" + "commitHash": "7f7dd78", + "buildTime": "2025-04-07T18:23:53.099Z" } \ No newline at end of file diff --git a/scripts/cleanup-codebase.js b/scripts/cleanup-codebase.js new file mode 100644 index 0000000..602f822 --- /dev/null +++ b/scripts/cleanup-codebase.js @@ -0,0 +1,80 @@ +/** + * Main script to clean up the codebase by finding deprecated imports and files + * Run with: node scripts/cleanup-codebase.js + */ + +const { execSync } = require('child_process'); +const path = require('path'); + +// Print a separator for readability +function printSeparator() { + console.log('\n' + '='.repeat(80) + '\n'); +} + +// Run a script and print its output +function runScript(scriptPath) { + try { + console.log(`Running ${path.basename(scriptPath)}...\n`); + const output = execSync(`node ${scriptPath}`, { encoding: 'utf8' }); + console.log(output); + } catch (error) { + console.error(`Error running ${scriptPath}:`, error.message); + console.error(error.stdout); + } +} + +// Main function to run all cleanup scripts +function main() { + console.log(` +✨ Ember Market Frontend Cleanup Utility ✨ + +This script will help you clean up your codebase by: +1. Finding deprecated imports that should be updated +2. Identifying files that can be safely removed +3. Organizing the lib folder into a proper structure + +`); + + // First, find deprecated imports + printSeparator(); + console.log('STEP 1: FINDING DEPRECATED IMPORTS'); + printSeparator(); + runScript(path.join(__dirname, 'find-deprecated-imports.js')); + + // Then, identify files that can be removed + printSeparator(); + console.log('STEP 2: IDENTIFYING DEPRECATED FILES'); + printSeparator(); + runScript(path.join(__dirname, 'cleanup-deprecated-files.js')); + + // Finally, organize the lib folder + printSeparator(); + console.log('STEP 3: ORGANIZING LIB FOLDER'); + printSeparator(); + runScript(path.join(__dirname, 'organize-lib-folder.js')); + + // Final instructions + printSeparator(); + console.log(` +NEXT STEPS: + +1. Review the lib directory to ensure files are properly organized +2. Verify that existing code still imports everything correctly +3. Run tests to ensure everything still works correctly + +The new structure is: +- lib/api.ts - Main API entry point that exports all API functions +- lib/utils/ - Contains utility functions organized by purpose +- lib/types/ - Contains TypeScript type definitions +- lib/services/ - Contains service implementations + +For ongoing maintenance: +- Always import from @/lib/api for API functionality +- Use @/lib/utils for utility functions +- Add new types to the appropriate types file +- Add new services to the services directory +`); +} + +// Run the main function +main(); \ No newline at end of file diff --git a/scripts/cleanup-deprecated-files.js b/scripts/cleanup-deprecated-files.js new file mode 100644 index 0000000..d0754b9 --- /dev/null +++ b/scripts/cleanup-deprecated-files.js @@ -0,0 +1,159 @@ +/** + * Script to help clean up deprecated files in the codebase + * Run with: node scripts/cleanup-deprecated-files.js + */ + +const fs = require('fs'); +const path = require('path'); + +// List of files that are now deprecated and can be removed/archived +const DEPRECATED_FILES = [ + // API Client files that are now merged into api-client.ts + 'lib/client-utils.ts', + 'lib/client-service.ts', + 'lib/data-service.ts', + + // Service files that are now in the services directory + 'lib/productData.ts', + 'lib/shippingHelper.ts', + 'lib/stats-service.ts', + 'lib/storeHelper.ts', + + // Server API files now in server-api.ts + 'lib/server-service.ts', + + // Files that may contain functionality already migrated + 'lib/api-utils.ts', +]; + +// Create backup directory if it doesn't exist +const BACKUP_DIR = path.join(__dirname, '../lib/deprecated-backup'); +if (!fs.existsSync(BACKUP_DIR)) { + fs.mkdirSync(BACKUP_DIR, { recursive: true }); + console.log(`Created backup directory: ${BACKUP_DIR}`); +} + +// Process each deprecated file +function processDeprecatedFiles() { + console.log('Analyzing deprecated files...\n'); + + const results = { + safe: [], + notFound: [], + mayNeedMigration: [] + }; + + DEPRECATED_FILES.forEach(filePath => { + const fullPath = path.join(__dirname, '..', filePath); + + if (!fs.existsSync(fullPath)) { + results.notFound.push(filePath); + return; + } + + // Check if this file is safe to remove (has been migrated) + const isSafeToRemove = checkIfSafeToRemove(filePath); + + if (isSafeToRemove) { + results.safe.push(filePath); + } else { + results.mayNeedMigration.push(filePath); + } + }); + + return results; +} + +// Check if a file's functionality has been migrated +function checkIfSafeToRemove(filePath) { + // Simple heuristic - files that we're confident have been fully migrated + const definitelyMigrated = [ + 'lib/client-utils.ts', + 'lib/client-service.ts', + 'lib/data-service.ts', + 'lib/productData.ts', + 'lib/shippingHelper.ts', + 'lib/stats-service.ts', + 'lib/server-service.ts', + ]; + + return definitelyMigrated.includes(filePath); +} + +// Create a backup of a file before removing it +function backupFile(filePath) { + const fullPath = path.join(__dirname, '..', filePath); + const backupPath = path.join(BACKUP_DIR, path.basename(filePath)); + + try { + const content = fs.readFileSync(fullPath, 'utf8'); + fs.writeFileSync(backupPath, content); + console.log(`✅ Backed up: ${filePath} to ${path.basename(backupPath)}`); + return true; + } catch (error) { + console.error(`❌ Failed to backup ${filePath}:`, error.message); + return false; + } +} + +// Remove a file from the codebase +function removeFile(filePath) { + const fullPath = path.join(__dirname, '..', filePath); + + try { + fs.unlinkSync(fullPath); + console.log(`🗑️ Removed: ${filePath}`); + return true; + } catch (error) { + console.error(`❌ Failed to remove ${filePath}:`, error.message); + return false; + } +} + +// Main execution function +function main() { + const results = processDeprecatedFiles(); + + console.log('\n=== ANALYSIS RESULTS ===\n'); + + if (results.notFound.length > 0) { + console.log('📂 Files not found:'); + results.notFound.forEach(f => console.log(` - ${f}`)); + console.log(''); + } + + if (results.mayNeedMigration.length > 0) { + console.log('⚠️ Files that may need migration:'); + results.mayNeedMigration.forEach(f => console.log(` - ${f}`)); + console.log('\nCheck these files manually to ensure all functionality has been migrated.\n'); + } + + if (results.safe.length > 0) { + console.log('✅ Files safe to remove (already migrated):'); + results.safe.forEach(f => console.log(` - ${f}`)); + + console.log('\nWhat would you like to do with these files?'); + console.log('1. Back up and remove now'); + console.log('2. Just back up'); + console.log('3. Do nothing for now'); + + // In a real script, you'd get user input here + // For now, just demonstrate with a backup + console.log('\nDemo mode: backing up files...\n'); + + results.safe.forEach(filePath => { + if (backupFile(filePath)) { + console.log(`To remove ${filePath}, run: rm "${path.join(__dirname, '..', filePath)}"`); + } + }); + } + + console.log('\n=== MANUAL CLEANUP INSTRUCTIONS ===\n'); + console.log('After verifying imports have been updated with find-deprecated-imports.js:'); + console.log('1. Remove the backed up files using the commands above'); + console.log('2. Check any remaining files in the lib/ directory to see if they should be organized'); + console.log('3. Update imports in files using the main API module'); +} + +// Run the script +main(); \ No newline at end of file diff --git a/scripts/find-deprecated-imports.js b/scripts/find-deprecated-imports.js new file mode 100644 index 0000000..c02cb85 --- /dev/null +++ b/scripts/find-deprecated-imports.js @@ -0,0 +1,89 @@ +/** + * Script to find deprecated imports in the codebase + * Run with: node scripts/find-deprecated-imports.js + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Deprecated patterns to search for +const DEPRECATED_PATTERNS = [ + "from '@/lib/client-utils'", + "from '@/lib/client-service'", + "from '@/lib/data-service'", + "from '@/lib/server-service'", + "from '@/lib/productData'", + "from '@/lib/shippingHelper'", + "from '@/lib/storeHelper'", + "from '@/lib/stats-service'", + // Add other deprecated import patterns here +]; + +// Run grep to find deprecated imports +function findDeprecatedImports() { + console.log('Searching for deprecated imports...\n'); + + DEPRECATED_PATTERNS.forEach(pattern => { + try { + // Using grep to find the pattern in all TypeScript/JavaScript files + const result = execSync(`grep -r "${pattern}" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" .`, { encoding: 'utf8' }); + + if (result) { + console.log(`\n== Found deprecated imports: ${pattern} ==`); + console.log(result); + console.log(`\n== Suggested replacement: ==`); + suggestReplacement(pattern); + } + } catch (error) { + // grep returns non-zero exit code if no matches found + if (error.status !== 1) { + console.error(`Error searching for ${pattern}:`, error.message); + } + } + }); +} + +function suggestReplacement(pattern) { + // Extract the import path + const match = pattern.match(/from '([^']+)'/); + if (!match) return; + + const oldPath = match[1]; + let replacement = ''; + + // Generate replacement suggestions based on deprecated path + switch (oldPath) { + case '@/lib/client-utils': + case '@/lib/client-service': + case '@/lib/data-service': + replacement = "import { clientFetch, fetchClient } from '@/lib/api';"; + break; + case '@/lib/server-service': + replacement = "import { fetchServer } from '@/lib/api'; // Only use in Server Components"; + break; + case '@/lib/productData': + replacement = "import { getProducts, createProduct, updateProduct, deleteProduct } from '@/lib/api';"; + break; + case '@/lib/shippingHelper': + replacement = "import { getShippingOptions, createShippingOption, updateShippingOption, deleteShippingOption } from '@/lib/api';"; + break; + case '@/lib/storeHelper': + replacement = "import { clientFetch } from '@/lib/api';"; + break; + case '@/lib/stats-service': + replacement = "import { getPlatformStats } from '@/lib/api';"; + break; + default: + replacement = "import from '@/lib/api';"; + } + + console.log(replacement); + console.log("\nReplace the specific imports with the corresponding ones from @/lib/api"); +} + +// Run the search +findDeprecatedImports(); + +// Uncomment the following line to run the remove-old-files.js script +// node scripts/remove-old-files.js \ No newline at end of file diff --git a/scripts/organize-lib-folder.js b/scripts/organize-lib-folder.js new file mode 100644 index 0000000..1fe1cbe --- /dev/null +++ b/scripts/organize-lib-folder.js @@ -0,0 +1,268 @@ +/** + * Script to organize the lib folder by moving files into appropriate directories + * Run with: node scripts/organize-lib-folder.js + */ + +const fs = require('fs'); +const path = require('path'); + +// File organization plan +const ORGANIZATION_PLAN = { + // Files to move to utils folder + 'utils': [ + { source: 'lib/utils.ts', destination: 'lib/utils/general.ts' }, + { source: 'lib/auth-utils.ts', destination: 'lib/utils/auth.ts' }, + { source: 'lib/git-utils.ts', destination: 'lib/utils/git.ts' }, + { source: 'lib/styles.ts', destination: 'lib/utils/styles.ts' } + ], + + // Files to move to types folder + 'types': [ + { source: 'lib/types.ts', destination: 'lib/types/index.ts' } + ], + + // Files to keep in lib root (core API files) + 'keep': [ + 'lib/api.ts', + 'lib/api-client.ts', + 'lib/server-api.ts', + 'lib/README.md' + ], + + // Files to remove (already migrated to services or consolidated) + 'remove': [ + 'lib/client-utils.ts', + 'lib/client-service.ts', + 'lib/data-service.ts', + 'lib/productData.ts', + 'lib/shippingHelper.ts', + 'lib/stats-service.ts', + 'lib/storeHelper.ts', + 'lib/server-service.ts', + 'lib/api-utils.ts' + ] +}; + +// Create backup directory +const BACKUP_DIR = path.join(__dirname, '../lib/deprecated-backup'); +if (!fs.existsSync(BACKUP_DIR)) { + fs.mkdirSync(BACKUP_DIR, { recursive: true }); + console.log(`Created backup directory: ${BACKUP_DIR}`); +} + +// Make sure target directories exist +function createDirectories() { + const directories = ['lib/utils', 'lib/types', 'lib/services']; + + directories.forEach(dir => { + const fullPath = path.join(__dirname, '..', dir); + if (!fs.existsSync(fullPath)) { + fs.mkdirSync(fullPath, { recursive: true }); + console.log(`Created directory: ${dir}`); + } + }); +} + +// Backup a file before moving or removing it +function backupFile(filePath) { + const fullPath = path.join(__dirname, '..', filePath); + + if (!fs.existsSync(fullPath)) { + console.log(`⚠️ ${filePath} doesn't exist, skipping backup`); + return false; + } + + try { + const backupPath = path.join(BACKUP_DIR, path.basename(filePath)); + fs.copyFileSync(fullPath, backupPath); + console.log(`✅ Backed up: ${filePath} to backup folder`); + return true; + } catch (error) { + console.error(`❌ Failed to backup ${filePath}:`, error.message); + return false; + } +} + +// Move a file to its new location +function moveFile(source, destination) { + const sourcePath = path.join(__dirname, '..', source); + const destPath = path.join(__dirname, '..', destination); + + if (!fs.existsSync(sourcePath)) { + console.log(`⚠️ Source file ${source} doesn't exist, skipping`); + return false; + } + + try { + // Ensure the destination directory exists + const destDir = path.dirname(destPath); + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + } + + // Read content from source + const content = fs.readFileSync(sourcePath, 'utf8'); + + // Write to destination + fs.writeFileSync(destPath, content); + console.log(`✅ Moved: ${source} → ${destination}`); + + // Remove the source file + fs.unlinkSync(sourcePath); + return true; + } catch (error) { + console.error(`❌ Error moving ${source} to ${destination}:`, error.message); + return false; + } +} + +// Remove a file +function removeFile(filePath) { + const fullPath = path.join(__dirname, '..', filePath); + + if (!fs.existsSync(fullPath)) { + console.log(`⚠️ ${filePath} doesn't exist, skipping removal`); + return false; + } + + try { + fs.unlinkSync(fullPath); + console.log(`🗑️ Removed: ${filePath}`); + return true; + } catch (error) { + console.error(`❌ Failed to remove ${filePath}:`, error.message); + return false; + } +} + +// Update imports in the utils/index.ts file +function updateUtilsIndex() { + const indexPath = path.join(__dirname, '..', 'lib/utils/index.ts'); + + try { + const content = `/** + * Main utilities index file + * Re-exports all utility functions from their respective modules + */ + +// Re-export general utils +export * from './general'; + +// Re-export auth utils +export * from './auth'; + +// Re-export git utils +export * from './git'; + +// Re-export style utils +export * from './styles'; +`; + + fs.writeFileSync(indexPath, content); + console.log(`✅ Updated lib/utils/index.ts with proper exports`); + return true; + } catch (error) { + console.error(`❌ Failed to update utils index:`, error.message); + return false; + } +} + +// Main function to organize files +function organizeLibFolder() { + console.log('Starting lib folder organization...\n'); + + // Create necessary directories + createDirectories(); + + // Stats to track progress + const stats = { + backed_up: 0, + moved: 0, + removed: 0, + errors: 0 + }; + + // First, back up all files we'll be modifying + const allFiles = [ + ...ORGANIZATION_PLAN.utils.map(item => item.source), + ...ORGANIZATION_PLAN.types.map(item => item.source), + ...ORGANIZATION_PLAN.remove + ]; + + allFiles.forEach(file => { + if (backupFile(file)) { + stats.backed_up++; + } else { + stats.errors++; + } + }); + + // Move files to utils folder + ORGANIZATION_PLAN.utils.forEach(item => { + if (moveFile(item.source, item.destination)) { + stats.moved++; + } else { + stats.errors++; + } + }); + + // Move files to types folder + ORGANIZATION_PLAN.types.forEach(item => { + if (moveFile(item.source, item.destination)) { + stats.moved++; + } else { + stats.errors++; + } + }); + + // Remove deprecated files + ORGANIZATION_PLAN.remove.forEach(file => { + if (removeFile(file)) { + stats.removed++; + } else { + stats.errors++; + } + }); + + // Update the utils index file + updateUtilsIndex(); + + // Summary + console.log(`\nOrganization complete!`); + console.log(`✅ ${stats.backed_up} files backed up`); + console.log(`✅ ${stats.moved} files moved to appropriate folders`); + console.log(`✅ ${stats.removed} deprecated files removed`); + + if (stats.errors > 0) { + console.log(`❌ ${stats.errors} errors encountered (see above)`); + } + + console.log(`\nBackups are located in: lib/deprecated-backup`); + console.log(`You can delete this directory when you are confident the migration is complete.`); +} + +// Show confirmation before proceeding +console.log(` +✨ Lib Folder Organization Tool ✨ + +This script will organize your lib folder as follows: + +1. Move utility files to lib/utils/ + ${ORGANIZATION_PLAN.utils.map(f => `\n - ${f.source} → ${f.destination}`).join('')} + +2. Move type files to lib/types/ + ${ORGANIZATION_PLAN.types.map(f => `\n - ${f.source} → ${f.destination}`).join('')} + +3. Keep core API files in the lib root + ${ORGANIZATION_PLAN.keep.map(f => `\n - ${f}`).join('')} + +4. Remove deprecated files + ${ORGANIZATION_PLAN.remove.map(f => `\n - ${f}`).join('')} + +All files will be backed up to lib/deprecated-backup before any changes. + +To proceed, edit this script and uncomment the organizeLibFolder() call at the bottom. +`); + +// Uncomment this line to actually organize the folder +organizeLibFolder(); \ No newline at end of file diff --git a/scripts/remove-old-files.js b/scripts/remove-old-files.js new file mode 100644 index 0000000..2618343 --- /dev/null +++ b/scripts/remove-old-files.js @@ -0,0 +1,102 @@ +/** + * Script to safely remove old files that have been migrated + * Run with: node scripts/remove-old-files.js + * + * This will backup and remove the files that are known to be safe to remove + * because they've been migrated to the new structure. + */ + +const fs = require('fs'); +const path = require('path'); + +// Files that are safe to remove because they've been completely migrated +const FILES_TO_REMOVE = [ + // Client API files + 'lib/client-utils.ts', + 'lib/client-service.ts', + 'lib/data-service.ts', + + // Service files + 'lib/productData.ts', + 'lib/shippingHelper.ts', + 'lib/stats-service.ts', + 'lib/server-service.ts', + + // API utility files + 'lib/api-utils.ts' +]; + +// Create backup directory +const BACKUP_DIR = path.join(__dirname, '../lib/deprecated-backup'); +if (!fs.existsSync(BACKUP_DIR)) { + fs.mkdirSync(BACKUP_DIR, { recursive: true }); + console.log(`Created backup directory: ${BACKUP_DIR}`); +} + +// Process a file - back it up and remove it +function processFile(filePath) { + const fullPath = path.join(__dirname, '..', filePath); + + if (!fs.existsSync(fullPath)) { + console.log(`⚠️ ${filePath} doesn't exist, skipping`); + return; + } + + try { + // 1. Create backup + const backupPath = path.join(BACKUP_DIR, path.basename(filePath)); + fs.copyFileSync(fullPath, backupPath); + console.log(`✅ Backed up ${filePath} to ${path.relative(path.join(__dirname, '..'), backupPath)}`); + + // 2. Remove the original file + fs.unlinkSync(fullPath); + console.log(`🗑️ Removed ${filePath}`); + + return true; + } catch (error) { + console.error(`❌ Error processing ${filePath}:`, error.message); + return false; + } +} + +// Process all files +function removeFiles() { + console.log('Starting cleanup of migrated files...\n'); + + let successCount = 0; + let errorCount = 0; + + FILES_TO_REMOVE.forEach(filePath => { + const result = processFile(filePath); + if (result) { + successCount++; + } else { + errorCount++; + } + }); + + console.log(`\nCleanup complete. Processed ${successCount + errorCount} files:`); + console.log(`✅ ${successCount} files successfully backed up and removed`); + + if (errorCount > 0) { + console.log(`❌ ${errorCount} files had errors (see above)`); + } + + console.log(`\nBackups are located in: ${path.relative(path.join(__dirname, '..'), BACKUP_DIR)}`); + console.log('You can delete this directory when you are confident the migration is complete.'); +} + +// Add confirmation prompt +console.log(` +⚠️ WARNING: This script will remove ${FILES_TO_REMOVE.length} files from your codebase ⚠️ + +All files will be backed up to ${path.relative(path.join(__dirname, '..'), BACKUP_DIR)} before removal. + +Files to remove: +${FILES_TO_REMOVE.map(f => `- ${f}`).join('\n')} + +To proceed, edit this script and uncomment the removeFiles() call at the bottom. +`); + +// Uncomment this line to actually remove the files +// removeFiles(); \ No newline at end of file diff --git a/scripts/update-imports.js b/scripts/update-imports.js new file mode 100644 index 0000000..5243987 --- /dev/null +++ b/scripts/update-imports.js @@ -0,0 +1,115 @@ +/** + * Script to update imports after reorganizing the lib folder + * Run with: node scripts/update-imports.js + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// Map of old imports to new imports +const importMap = { + '@/lib/styles': '@/lib/utils/styles', + '@/lib/utils': '@/lib/utils/general', + '@/lib/auth-utils': '@/lib/utils/auth', + '@/lib/git-utils': '@/lib/utils/git', + '@/lib/types': '@/lib/types', + '@/lib/data-service': '@/lib/api', + '@/lib/client-utils': '@/lib/api', + '@/lib/client-service': '@/lib/api', + '@/lib/productData': '@/lib/api', + '@/lib/shippingHelper': '@/lib/api', + '@/lib/stats-service': '@/lib/api', + '@/lib/storeHelper': '@/lib/api', + '@/lib/server-service': '@/lib/api', + '@/lib/api-utils': '@/lib/api', +}; + +// Function to find all TypeScript and JavaScript files +function findTsJsFiles(directory) { + let results = []; + const files = fs.readdirSync(directory, { withFileTypes: true }); + + for (const file of files) { + const fullPath = path.join(directory, file.name); + + // Skip node_modules and .next directories + if (file.isDirectory()) { + if (file.name !== 'node_modules' && file.name !== '.next' && file.name !== '.git') { + results = results.concat(findTsJsFiles(fullPath)); + } + } else if (file.name.endsWith('.ts') || file.name.endsWith('.tsx') || file.name.endsWith('.js') || file.name.endsWith('.jsx')) { + results.push(fullPath); + } + } + + return results; +} + +// Function to update imports in a file +function updateImportsInFile(filePath) { + console.log(`Checking ${filePath}`); + let fileContent = fs.readFileSync(filePath, 'utf8'); + let originalContent = fileContent; + let hasChanges = false; + + // Check for each old import and replace with new import + for (const [oldImport, newImport] of Object.entries(importMap)) { + // Create regex to match import statements for this module + // This handles different import formats: + // import X from 'oldImport' + // import { X } from 'oldImport' + // import * as X from 'oldImport' + const importRegex = new RegExp(`import\\s+(?:.+?)\\s+from\\s+['"]${oldImport.replace('/', '\\/')}['"]`, 'g'); + + if (importRegex.test(fileContent)) { + fileContent = fileContent.replace(importRegex, (match) => { + return match.replace(oldImport, newImport); + }); + hasChanges = true; + } + } + + // If any changes were made, write the file + if (hasChanges) { + fs.writeFileSync(filePath, fileContent, 'utf8'); + console.log(`✅ Updated imports in ${filePath}`); + return true; + } + + return false; +} + +// Main function to update all imports +function updateAllImports() { + console.log('🔎 Finding TypeScript and JavaScript files...'); + const projectRoot = path.resolve(__dirname, '..'); + const files = findTsJsFiles(projectRoot); + + console.log(`Found ${files.length} files to check.`); + console.log('🔄 Updating imports...'); + + let updatedCount = 0; + + for (const file of files) { + if (updateImportsInFile(file)) { + updatedCount++; + } + } + + console.log(`\n✅ Import update complete!`); + console.log(`- ${updatedCount} files were updated`); + console.log(`- ${files.length - updatedCount} files had no changes needed`); + + // Run next build to check for any remaining errors + console.log('\n🔍 Running TypeScript check to find any remaining issues...'); + try { + execSync('npx tsc --noEmit', { stdio: 'inherit' }); + console.log('\n✅ TypeScript check passed!'); + } catch (error) { + console.error('\n⚠️ TypeScript check found some issues. Please fix them manually.'); + } +} + +// Run the script +updateAllImports(); \ No newline at end of file diff --git a/services/customerService.ts b/services/customerService.ts index 17b4fc8..7152329 100644 --- a/services/customerService.ts +++ b/services/customerService.ts @@ -1,30 +1,8 @@ -import { clientFetch } from '@/lib/client-utils'; - -export interface CustomerStats { - userId: string; - telegramUserId: number; - telegramUsername: string; - totalOrders: number; - totalSpent: number; - ordersByStatus: { - paid: number; - completed: number; - acknowledged: number; - shipped: number; - }; - lastOrderDate: string | null; - firstOrderDate: string; - chatId: number; - hasOrders?: boolean; -} - -export const getCustomers = async (page: number = 1, limit: number = 25): Promise<{ - customers: CustomerStats[]; - total: number; -}> => { - return clientFetch(`/customers?page=${page}&limit=${limit}`); -}; - -export const getCustomerDetails = async (userId: string): Promise => { - return clientFetch(`/customers/${userId}`); -}; \ No newline at end of file +// This file is maintained for backward compatibility +// Import from the new consolidated API +export { + getCustomers, + getCustomerDetails, + type CustomerStats, + type CustomerResponse +} from '../lib/api'; \ No newline at end of file diff --git a/services/index.ts b/services/index.ts new file mode 100644 index 0000000..b4117f5 --- /dev/null +++ b/services/index.ts @@ -0,0 +1,3 @@ +// Re-export all services from the lib directory +// This provides backward compatibility +export * from '../lib/api'; \ No newline at end of file