weewoo
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,3 +39,4 @@ next-env.d.ts
|
|||||||
*.zip
|
*.zip
|
||||||
.env
|
.env
|
||||||
env.local
|
env.local
|
||||||
|
/lib/deprecated-backup
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { fetchData } from "@/lib/data-service";
|
import { fetchData } from "@/lib/api";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { apiRequest } from "@/lib/storeHelper";
|
import { apiRequest } from "@/lib/api";
|
||||||
import type { Category } from "@/models/categories";
|
import type { Category } from "@/models/categories";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { fetchData } from '@/lib/data-service';
|
import { fetchData } from '@/lib/api';
|
||||||
import { clientFetch } from '@/lib/client-utils';
|
import { clientFetch } from '@/lib/api';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Dashboard from "@/components/dashboard/dashboard";
|
import Dashboard from "@/components/dashboard/dashboard";
|
||||||
import Content from "@/components/dashboard/content";
|
import Content from "@/components/dashboard/content";
|
||||||
import { fetchServer } from '@/lib/server-service';
|
import { fetchServer } from '@/lib/api';
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
import { Info, GitCommit, User, Zap } from 'lucide-react';
|
import { Info, GitCommit, User, Zap } from 'lucide-react';
|
||||||
import packageJson from '../../package.json';
|
import packageJson from '../../package.json';
|
||||||
import { getGitCommitInfo } from '@/lib/git-utils';
|
import { getGitInfo, getShortGitHash } from '@/lib/utils/git';
|
||||||
|
|
||||||
// ✅ Corrected Vendor Type
|
// ✅ Corrected Vendor Type
|
||||||
interface Vendor {
|
interface Vendor {
|
||||||
@@ -29,16 +29,18 @@ interface OrderStats {
|
|||||||
export default async function DashboardPage() {
|
export default async function DashboardPage() {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
const [userResponse, orderStats, gitInfo] = await Promise.all([
|
const [userResponse, orderStats] = await Promise.all([
|
||||||
fetchServer<User>("/auth/me"),
|
fetchServer<User>("/auth/me"),
|
||||||
fetchServer<OrderStats>("/orders/stats"),
|
fetchServer<OrderStats>("/orders/stats")
|
||||||
getGitCommitInfo()
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Get git info using the new utility
|
||||||
|
const gitInfo = getGitInfo();
|
||||||
|
|
||||||
const endTime = performance.now();
|
const endTime = performance.now();
|
||||||
const generationTime = (endTime - startTime).toFixed(2);
|
const generationTime = (endTime - startTime).toFixed(2);
|
||||||
const panelVersion = packageJson.version;
|
const panelVersion = packageJson.version;
|
||||||
const commitHash = gitInfo.commitHash;
|
const commitHash = gitInfo.hash;
|
||||||
|
|
||||||
const vendor = userResponse.vendor;
|
const vendor = userResponse.vendor;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
saveProductImage,
|
saveProductImage,
|
||||||
deleteProductData,
|
deleteProductData,
|
||||||
} from "@/lib/productData";
|
} from "@/lib/productData";
|
||||||
import { clientFetch } from "@/lib/client-utils";
|
import { clientFetch } from "@/lib/api";
|
||||||
import { ProductModal } from "@/components/modals/product-modal";
|
import { ProductModal } from "@/components/modals/product-modal";
|
||||||
import ProductTable from "@/components/tables/product-table";
|
import ProductTable from "@/components/tables/product-table";
|
||||||
import { Category } from "@/models/categories";
|
import { Category } from "@/models/categories";
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import {
|
|||||||
addShippingMethod,
|
addShippingMethod,
|
||||||
deleteShippingMethod,
|
deleteShippingMethod,
|
||||||
updateShippingMethod,
|
updateShippingMethod,
|
||||||
} from "@/lib/shippingHelper";
|
ShippingMethod,
|
||||||
|
ShippingData
|
||||||
import { ShippingMethod, ShippingData } from "@/lib/types";
|
} from "@/lib/services/shipping-service";
|
||||||
|
|
||||||
import { ShippingTable } from "@/components/tables/shipping-table";
|
import { ShippingTable } from "@/components/tables/shipping-table";
|
||||||
|
|
||||||
@@ -89,8 +89,8 @@ export default function ShippingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await addShippingMethod(
|
await addShippingMethod(
|
||||||
authToken,
|
newShipping,
|
||||||
newShipping
|
authToken
|
||||||
);
|
);
|
||||||
|
|
||||||
// Close modal and reset form before refreshing to avoid UI delays
|
// Close modal and reset form before refreshing to avoid UI delays
|
||||||
@@ -125,9 +125,9 @@ export default function ShippingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await updateShippingMethod(
|
await updateShippingMethod(
|
||||||
authToken,
|
|
||||||
newShipping._id,
|
newShipping._id,
|
||||||
newShipping
|
newShipping,
|
||||||
|
authToken
|
||||||
);
|
);
|
||||||
|
|
||||||
// Close modal and reset form before refreshing to avoid UI delays
|
// Close modal and reset form before refreshing to avoid UI delays
|
||||||
@@ -150,7 +150,7 @@ export default function ShippingPage() {
|
|||||||
const handleDeleteShipping = async (_id: string) => {
|
const handleDeleteShipping = async (_id: string) => {
|
||||||
try {
|
try {
|
||||||
const authToken = document.cookie.split("Authorization=")[1];
|
const authToken = document.cookie.split("Authorization=")[1];
|
||||||
const response = await deleteShippingMethod(authToken, _id);
|
const response = await deleteShippingMethod(_id, authToken);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
refreshShippingMethods(); // Refresh the list after deleting
|
refreshShippingMethods(); // Refresh the list after deleting
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import {
|
|||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Product } from "@/models/products";
|
import { Product } from "@/models/products";
|
||||||
import { Package, RefreshCw, ChevronDown, CheckSquare, XSquare } from "lucide-react";
|
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";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export default function StockManagementPage() {
|
export default function StockManagementPage() {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
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 { formatCurrency } from "@/utils/format";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Save, Send, Key, MessageSquare, Shield, Globe, Wallet } from "lucide-react";
|
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 { toast, Toaster } from "sonner";
|
||||||
import BroadcastDialog from "@/components/modals/broadcast-dialog";
|
import BroadcastDialog from "@/components/modals/broadcast-dialog";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowRight, Shield, LineChart, Zap } from "lucide-react";
|
import { ArrowRight, Shield, LineChart, Zap } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { fetchPlatformStats } from "@/lib/stats-service";
|
import { getPlatformStatsServer } from "@/lib/api";
|
||||||
import { HomeNavbar } from "@/components/home-navbar";
|
import { HomeNavbar } from "@/components/home-navbar";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { AnimatedStatsSection } from "@/components/animated-stats-section";
|
import { AnimatedStatsSection } from "@/components/animated-stats-section";
|
||||||
@@ -31,7 +31,7 @@ function formatCurrencyValue(amount: number): string {
|
|||||||
// This is a server component
|
// This is a server component
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
try {
|
try {
|
||||||
const stats = await fetchPlatformStats();
|
const stats = await getPlatformStatsServer();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-black text-white">
|
<div className="flex flex-col min-h-screen bg-black text-white">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { clientFetch } from "@/lib/client-utils";
|
import { clientFetch } from "@/lib/api";
|
||||||
|
|
||||||
const KeepOnline = () => {
|
const KeepOnline = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { Package, Users, CreditCard } from "lucide-react";
|
import { Package, Users, CreditCard } from "lucide-react";
|
||||||
import { AnimatedCounter } from "./animated-counter";
|
import { AnimatedCounter } from "./animated-counter";
|
||||||
import { PlatformStats } from "@/lib/stats-service";
|
import { PlatformStats } from "@/lib/api";
|
||||||
|
|
||||||
const formatCurrency = (value: number) => {
|
const formatCurrency = (value: number) => {
|
||||||
return new Intl.NumberFormat('en-GB', {
|
return new Intl.NumberFormat('en-GB', {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { getCookie } from "@/lib/client-utils";
|
import { getCookie } from "@/lib/api";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils/general";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { ArrowLeft, Send, RefreshCw, File, FileText, Image as ImageIcon, Download } from "lucide-react";
|
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 { ImageViewerModal } from "@/components/modals/image-viewer-modal";
|
||||||
import BuyerOrderInfo from "./BuyerOrderInfo";
|
import BuyerOrderInfo from "./BuyerOrderInfo";
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { clientFetch } from "@/lib/client-utils";
|
import { clientFetch } from "@/lib/api";
|
||||||
|
|
||||||
interface UnreadCounts {
|
interface UnreadCounts {
|
||||||
totalUnread: number;
|
totalUnread: number;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from 'next/navigation';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -22,7 +22,15 @@ import {
|
|||||||
Eye,
|
Eye,
|
||||||
User,
|
User,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight
|
ChevronRight,
|
||||||
|
MessageSquare,
|
||||||
|
ArrowRightCircle,
|
||||||
|
X,
|
||||||
|
Clock,
|
||||||
|
CheckCheck,
|
||||||
|
Search,
|
||||||
|
Volume2,
|
||||||
|
VolumeX
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -31,9 +39,11 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { clientFetch } from "@/lib/client-utils";
|
|
||||||
import { toast } from "sonner";
|
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 {
|
interface Chat {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { ArrowLeft, Send, RefreshCw, Search, User } from "lucide-react";
|
import { ArrowLeft, Send, RefreshCw, Search, User } from "lucide-react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { getCookie } from "@/lib/client-utils";
|
import { getCookie } from "@/lib/api";
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import OrderStats from "./order-stats"
|
import OrderStats from "./order-stats"
|
||||||
import { getGreeting } from "@/lib/utils"
|
import { getGreeting } from "@/lib/utils/general"
|
||||||
import { statsConfig } from "@/config/dashboard"
|
import { statsConfig } from "@/config/dashboard"
|
||||||
import { getRandomQuote } from "@/config/quotes"
|
import { getRandomQuote } from "@/config/quotes"
|
||||||
import type { OrderStatsData } from "@/lib/types"
|
import type { OrderStatsData } from "@/lib/types"
|
||||||
@@ -11,7 +11,7 @@ import { ShoppingCart, RefreshCcw } from "lucide-react"
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { Skeleton } from "@/components/ui/skeleton"
|
import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import { clientFetch } from "@/lib/client-utils"
|
import { clientFetch } from "@/lib/api"
|
||||||
|
|
||||||
interface ContentProps {
|
interface ContentProps {
|
||||||
username: string
|
username: string
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { toast } from '@/components/ui/use-toast';
|
import { toast } from '@/components/ui/use-toast';
|
||||||
import { Promotion, PromotionFormData } from '@/lib/types/promotion';
|
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)
|
// Form schema validation with Zod (same as NewPromotionForm)
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { Textarea } from '@/components/ui/textarea';
|
import { Textarea } from '@/components/ui/textarea';
|
||||||
import { toast } from '@/components/ui/use-toast';
|
import { toast } from '@/components/ui/use-toast';
|
||||||
import { PromotionFormData } from '@/lib/types/promotion';
|
import { PromotionFormData } from '@/lib/types/promotion';
|
||||||
import { fetchClient } from '@/lib/client-service';
|
import { fetchClient } from '@/lib/api';
|
||||||
|
|
||||||
// Form schema validation with Zod
|
// Form schema validation with Zod
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
import { toast } from '@/components/ui/use-toast';
|
import { toast } from '@/components/ui/use-toast';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Promotion } from '@/lib/types/promotion';
|
import { Promotion } from '@/lib/types/promotion';
|
||||||
import { fetchClient } from '@/lib/client-service';
|
import { fetchClient } from '@/lib/api';
|
||||||
import NewPromotionForm from './NewPromotionForm';
|
import NewPromotionForm from './NewPromotionForm';
|
||||||
import EditPromotionForm from './EditPromotionForm';
|
import EditPromotionForm from './EditPromotionForm';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ShoppingCart, LogOut } from "lucide-react"
|
|||||||
import { NavItem } from "./nav-item"
|
import { NavItem } from "./nav-item"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { sidebarConfig } from "@/config/sidebar"
|
import { sidebarConfig } from "@/config/sidebar"
|
||||||
import { logoutUser } from "@/lib/auth-utils"
|
import { logoutUser } from "@/lib/utils/auth"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
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 { Send, Bold, Italic, Code, Link as LinkIcon, Image as ImageIcon, X, Eye, EyeOff } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { apiRequest } from "@/lib/storeHelper";
|
import { apiRequest } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils/general";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import type { ProductModalProps, ProductData } from "@/lib/types";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { apiRequest } from "@/lib/storeHelper";
|
import { apiRequest } from "@/lib/api";
|
||||||
|
|
||||||
type CategorySelectProps = {
|
type CategorySelectProps = {
|
||||||
categories: { _id: string; name: string; parentId?: string }[];
|
categories: { _id: string; name: string; parentId?: string }[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { clientFetch } from "@/lib/client-utils";
|
import { clientFetch } from "@/lib/api";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Package, Bell } from "lucide-react";
|
import { Package, Bell } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
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 { toast } from "sonner";
|
||||||
import { getCookie } from "@/lib/client-utils";
|
import { getCookie } from "@/lib/api";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
interface Order {
|
interface Order {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
MessageCircle
|
MessageCircle
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { clientFetch } from '@/lib/client-utils';
|
import { clientFetch } from '@/lib/api';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
import { ChevronDown } from "lucide-react"
|
import { ChevronDown } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Accordion = AccordionPrimitive.Root
|
const Accordion = AccordionPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
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"
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
const AlertDialog = AlertDialogPrimitive.Root
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const alertVariants = cva(
|
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",
|
"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",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Avatar = React.forwardRef<
|
const Avatar = React.forwardRef<
|
||||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cva, type VariantProps } from "class-variance-authority";
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
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",
|
"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",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from "react"
|
|||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Breadcrumb = React.forwardRef<
|
const Breadcrumb = React.forwardRef<
|
||||||
HTMLElement,
|
HTMLElement,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as React from "react"
|
|||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
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",
|
"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",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
import { DayPicker } from "react-day-picker"
|
import { DayPicker } from "react-day-picker"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
import { buttonVariants } from "@/components/ui/button"
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
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) => (
|
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} />
|
<div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import useEmblaCarousel, {
|
|||||||
} from "embla-carousel-react"
|
} from "embla-carousel-react"
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1]
|
type CarouselApi = UseEmblaCarouselType[1]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as RechartsPrimitive from "recharts"
|
import * as RechartsPrimitive from "recharts"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
const THEMES = { light: "", dark: ".dark" } as const
|
const THEMES = { light: "", dark: ".dark" } as const
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
import { Check } from "lucide-react"
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<
|
const Checkbox = React.forwardRef<
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { type DialogProps } from "@radix-ui/react-dialog"
|
|||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
import { Search } from "lucide-react"
|
import { Search } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
const Command = React.forwardRef<
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const ContextMenu = ContextMenuPrimitive.Root
|
const ContextMenu = ContextMenuPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Drawer as DrawerPrimitive } from "vaul"
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Drawer = ({
|
const Drawer = ({
|
||||||
shouldScaleBackground = true,
|
shouldScaleBackground = true,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
useFormContext,
|
useFormContext,
|
||||||
} from "react-hook-form"
|
} from "react-hook-form"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva(
|
||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
import * as MenubarPrimitive from "@radix-ui/react-menubar"
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const MenubarMenu = MenubarPrimitive.Menu
|
const MenubarMenu = MenubarPrimitive.Menu
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
|||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority"
|
||||||
import { ChevronDown } from "lucide-react"
|
import { ChevronDown } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const NavigationMenu = React.forwardRef<
|
const NavigationMenu = React.forwardRef<
|
||||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-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"
|
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Popover = PopoverPrimitive.Root
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Progress = React.forwardRef<
|
const Progress = React.forwardRef<
|
||||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||||
import { Circle } from "lucide-react"
|
import { Circle } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const RadioGroup = React.forwardRef<
|
const RadioGroup = React.forwardRef<
|
||||||
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
React.ElementRef<typeof RadioGroupPrimitive.Root>,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { GripVertical } from "lucide-react"
|
import { GripVertical } from "lucide-react"
|
||||||
import * as ResizablePrimitive from "react-resizable-panels"
|
import * as ResizablePrimitive from "react-resizable-panels"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const ResizablePanelGroup = ({
|
const ResizablePanelGroup = ({
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const ScrollArea = React.forwardRef<
|
const ScrollArea = React.forwardRef<
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Separator = React.forwardRef<
|
const Separator = React.forwardRef<
|
||||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog"
|
|||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Sheet = SheetPrimitive.Root
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { VariantProps, cva } from "class-variance-authority"
|
|||||||
import { PanelLeft } from "lucide-react"
|
import { PanelLeft } from "lucide-react"
|
||||||
|
|
||||||
import { useIsMobile } from "@/hooks/use-mobile"
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
function Skeleton({
|
function Skeleton({
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Slider = React.forwardRef<
|
const Slider = React.forwardRef<
|
||||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Switch = React.forwardRef<
|
const Switch = React.forwardRef<
|
||||||
React.ElementRef<typeof SwitchPrimitives.Root>,
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Table = React.forwardRef<
|
const Table = React.forwardRef<
|
||||||
HTMLTableElement,
|
HTMLTableElement,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const Textarea = React.forwardRef<
|
const Textarea = React.forwardRef<
|
||||||
HTMLTextAreaElement,
|
HTMLTextAreaElement,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as ToastPrimitives from "@radix-ui/react-toast"
|
|||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { X } from "lucide-react"
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const ToastProvider = ToastPrimitives.Provider
|
const ToastProvider = ToastPrimitives.Provider
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||||
import { type VariantProps } from "class-variance-authority"
|
import { type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
import { toggleVariants } from "@/components/ui/toggle"
|
import { toggleVariants } from "@/components/ui/toggle"
|
||||||
|
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as React from "react"
|
|||||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const toggleVariants = cva(
|
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",
|
"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",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
import { cn } from "@/lib/styles";
|
import { cn } from "@/lib/utils/styles";
|
||||||
|
|
||||||
const TooltipProvider = TooltipPrimitive.Provider
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
|||||||
82
lib/README.md
Normal file
82
lib/README.md
Normal 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
264
lib/api-client.ts
Normal 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}`);
|
||||||
|
};
|
||||||
@@ -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
136
lib/api.ts
Normal 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;
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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];
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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 { 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
|
* Constructs a server-side API URL for backend requests
|
||||||
@@ -21,13 +33,23 @@ function getServerApiUrl(endpoint: string): string {
|
|||||||
* Server-side fetch wrapper with authentication.
|
* Server-side fetch wrapper with authentication.
|
||||||
* Used in Server Components to make authenticated API requests to the backend.
|
* 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.
|
* 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>(
|
export async function fetchServer<T = unknown>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
options: RequestInit = {}
|
options: RequestInit = {}
|
||||||
): Promise<T> {
|
): 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
|
// Get auth token from cookies
|
||||||
const cookieStore = await cookies();
|
const cookieStore = cookiesModule.cookies();
|
||||||
const authToken = cookieStore.get('Authorization')?.value;
|
const authToken = cookieStore.get('Authorization')?.value;
|
||||||
|
|
||||||
// Redirect to login if not authenticated
|
// Redirect to login if not authenticated
|
||||||
@@ -68,4 +90,42 @@ export async function fetchServer<T = unknown>(
|
|||||||
console.error(`Server request to ${endpoint} failed:`, error);
|
console.error(`Server request to ${endpoint} failed:`, error);
|
||||||
throw 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
5
lib/services/index.ts
Normal 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';
|
||||||
101
lib/services/product-service.ts
Normal file
101
lib/services/product-service.ts
Normal 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),
|
||||||
|
});
|
||||||
|
};
|
||||||
130
lib/services/shipping-service.ts
Normal file
130
lib/services/shipping-service.ts
Normal 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;
|
||||||
47
lib/services/stats-service.ts
Normal file
47
lib/services/stats-service.ts
Normal 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');
|
||||||
|
};
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -59,6 +59,7 @@ export interface PricingTier {
|
|||||||
export interface Category {
|
export interface Category {
|
||||||
_id: string
|
_id: string
|
||||||
name: string
|
name: string
|
||||||
|
parentId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderStatsData {
|
export interface OrderStatsData {
|
||||||
50
lib/utils/git.ts
Normal file
50
lib/utils/git.ts
Normal 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
16
lib/utils/index.ts
Normal 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';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "8da4d00",
|
"commitHash": "7f7dd78",
|
||||||
"buildTime": "2025-04-06T15:28:50.532Z"
|
"buildTime": "2025-04-07T18:23:53.099Z"
|
||||||
}
|
}
|
||||||
80
scripts/cleanup-codebase.js
Normal file
80
scripts/cleanup-codebase.js
Normal 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();
|
||||||
159
scripts/cleanup-deprecated-files.js
Normal file
159
scripts/cleanup-deprecated-files.js
Normal 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();
|
||||||
89
scripts/find-deprecated-imports.js
Normal file
89
scripts/find-deprecated-imports.js
Normal 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
|
||||||
268
scripts/organize-lib-folder.js
Normal file
268
scripts/organize-lib-folder.js
Normal 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
102
scripts/remove-old-files.js
Normal 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
115
scripts/update-imports.js
Normal 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
Reference in New Issue
Block a user