This commit is contained in:
NotII
2025-04-07 19:25:24 +01:00
parent 7f7dd78818
commit 2f48ee38c2
102 changed files with 1825 additions and 761 deletions

1
.gitignore vendored
View File

@@ -39,3 +39,4 @@ next-env.d.ts
*.zip
.env
env.local
/lib/deprecated-backup

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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<User>("/auth/me"),
fetchServer<OrderStats>("/orders/stats"),
getGitCommitInfo()
fetchServer<OrderStats>("/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;

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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() {

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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 (
<div className="flex flex-col min-h-screen bg-black text-white">

View File

@@ -1,7 +1,7 @@
"use client";
import { useEffect } from "react";
import { clientFetch } from "@/lib/client-utils";
import { clientFetch } from "@/lib/api";
const KeepOnline = () => {
useEffect(() => {

View File

@@ -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', {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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

View File

@@ -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({

View File

@@ -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({

View File

@@ -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';

View File

@@ -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 = () => {

View File

@@ -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';

View File

@@ -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 }[];

View File

@@ -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";

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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<typeof AvatarPrimitive.Root>,

View File

@@ -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",

View File

@@ -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,

View File

@@ -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",

View File

@@ -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<typeof DayPicker>

View File

@@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils/general"
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />

View File

@@ -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]

View File

@@ -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

View File

@@ -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<typeof CheckboxPrimitive.Root>,

View File

@@ -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<

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/styles";
import { cn } from "@/lib/utils/styles";
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ className, type, ...props }, ref) => {

View File

@@ -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"

View File

@@ -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

View File

@@ -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<typeof NavigationMenuPrimitive.Root>,

View File

@@ -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">) => (

View File

@@ -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

View File

@@ -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<typeof ProgressPrimitive.Root>,

View File

@@ -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<typeof RadioGroupPrimitive.Root>,

View File

@@ -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,

View File

@@ -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<typeof ScrollAreaPrimitive.Root>,

View File

@@ -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

View File

@@ -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<typeof SeparatorPrimitive.Root>,

View File

@@ -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

View File

@@ -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"

View File

@@ -1,4 +1,4 @@
import { cn } from "@/lib/styles";
import { cn } from "@/lib/utils/styles";
function Skeleton({
className,

View File

@@ -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<typeof SliderPrimitive.Root>,

View File

@@ -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<typeof SwitchPrimitives.Root>,

View File

@@ -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,

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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<

View File

@@ -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",

View File

@@ -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

82
lib/README.md Normal file
View File

@@ -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.

264
lib/api-client.ts Normal file
View File

@@ -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<string, string> = {}): 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<T = any>(url: string, options: RequestInit = {}): Promise<T> {
try {
// Create headers with authentication
const headers = createApiHeaders(null, options.headers as Record<string, string>);
// 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<T>(
endpoint: string,
options: FetchOptions = {}
): Promise<T> {
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<string, string> = {
'Content-Type': 'application/json',
...(headers as Record<string, string>),
};
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<CustomerResponse> => {
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<CustomerStats> => {
return clientFetch(`/customers/${userId}`);
};

View File

@@ -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<string, string> = {}): 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;
}

136
lib/api.ts Normal file
View File

@@ -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<string, string> } = {
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 = <T>(url: string, options?: RequestInit) => Promise<T>;
type CustomerServerFn = (options?: any) => Promise<any>;
type CustomerDetailServerFn = (id: string, options?: any) => Promise<any>;
type PlatformStatsServerFn = () => Promise<any>;
// 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;

View File

@@ -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<T>(
endpoint: string,
options: FetchOptions = {}
): Promise<T> {
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<string, string> = {
'Content-Type': 'application/json',
...(headers as Record<string, string>),
};
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;
}
}

View File

@@ -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<string, string> = {}): 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<T = any>(url: string, options: RequestInit = {}): Promise<T> {
try {
// Create headers with authentication
const headers = createApiHeaders(null, options.headers as Record<string, string>);
// 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];
}

View File

@@ -1,13 +0,0 @@
/**
* Client-side fetch function for API requests.
*/
export async function fetchData(url: string, options: RequestInit = {}): Promise<any> {
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;
}
}

View File

@@ -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<GitInfo> {
return gitInfo;
}

View File

@@ -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;
}
};

View File

@@ -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<T = unknown>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
// 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<T = unknown>(
console.error(`Server request to ${endpoint} failed:`, error);
throw error;
}
}
}
// =========== 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<CustomerResponse> => {
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<CustomerStats> => {
return fetchServer(`/customers/${userId}`);
};
// Server-side platform stats function
export async function getPlatformStatsServer(): Promise<any> {
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
};
}
}

5
lib/services/index.ts Normal file
View File

@@ -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';

View File

@@ -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<ProductsResponse> => {
return clientFetch(`/products?page=${page}&limit=${limit}`);
};
/**
* Get a specific product by ID
*/
export const getProductDetails = async (productId: string): Promise<Product> => {
return clientFetch(`/products/${productId}`);
};
/**
* Create a new product
*/
export const createProduct = async (productData: Omit<Product, '_id'>): Promise<Product> => {
return clientFetch('/products', {
method: 'POST',
body: JSON.stringify(productData),
});
};
/**
* Update a product
*/
export const updateProduct = async (productId: string, productData: Partial<Product>): Promise<Product> => {
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<StockData> => {
return clientFetch(`/stock/${productId}`);
};
/**
* Update product stock
*/
export const updateProductStock = async (productId: string, stockData: StockData): Promise<StockData> => {
return clientFetch(`/stock/${productId}`, {
method: 'PUT',
body: JSON.stringify(stockData),
});
};

View File

@@ -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<ShippingOption[]> => {
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<ShippingOptionsResponse>('/api/shipping', options);
return response.data || [];
};
// Get a single shipping option
export const getShippingOption = async (id: string, authToken?: string): Promise<ShippingOption> => {
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;

View File

@@ -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<PlatformStats> => {
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<any> => {
return clientFetch('/stats/vendor');
};

View File

@@ -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<ShippingMethod, "_id">
): Promise<ShippingMethod[]> => {
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;
}
};

View File

@@ -1,51 +0,0 @@
export interface PlatformStats {
orders: {
completed: number;
};
vendors: {
total: number;
};
transactions: {
volume: number;
averageOrderValue: number;
};
}
export async function fetchPlatformStats(): Promise<PlatformStats> {
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
}
};
}
}

View File

@@ -1,53 +0,0 @@
import { fetchData } from '@/lib/data-service';
export const apiRequest = async <T = any>(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");
}
};

View File

@@ -59,6 +59,7 @@ export interface PricingTier {
export interface Category {
_id: string
name: string
parentId?: string
}
export interface OrderStatsData {

50
lib/utils/git.ts Normal file
View File

@@ -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();
}

16
lib/utils/index.ts Normal file
View File

@@ -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';

View File

@@ -1,4 +1,4 @@
{
"commitHash": "8da4d00",
"buildTime": "2025-04-06T15:28:50.532Z"
"commitHash": "7f7dd78",
"buildTime": "2025-04-07T18:23:53.099Z"
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

102
scripts/remove-old-files.js Normal file
View File

@@ -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();

115
scripts/update-imports.js Normal file
View File

@@ -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();

Some files were not shown because too many files have changed in this diff Show More