Add CSV export for orders and update UI symbols

Introduces an exportOrdersToCSV function in lib/api-client.ts to allow exporting orders by status as a CSV file. Updates various UI components to use the '•' (bullet) symbol instead of '·' (middle dot) and replaces some emoji/unicode characters for improved consistency and compatibility. Also normalizes the 'use client' directive to include a BOM in many files.
This commit is contained in:
g
2025-12-15 17:57:18 +00:00
parent 07dcaf55c0
commit 0176f89cb7
91 changed files with 232 additions and 136 deletions

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useKeepOnline } from "@/hooks/useKeepOnline";
@@ -14,4 +14,4 @@ const KeepOnline = () => {
return null;
}
export default KeepOnline;
export default KeepOnline;

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import React, { useState, useEffect } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
@@ -739,4 +739,4 @@ export default function AdminAnalytics() {
</Tabs>
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState } from "react";
import { fetchClient } from "@/lib/api-client";

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useEffect, useState } from "react";
import { fetchClient } from "@/lib/api-client";
@@ -62,7 +62,7 @@ export default function InvitationsListCard() {
Code: <span className="font-mono px-1.5 py-0.5 rounded bg-muted">{inv.code}</span>
</div>
<div className="text-xs text-muted-foreground">
Created: {new Date(inv.createdAt).toLocaleString()} · Expires: {new Date(inv.expiresAt).toLocaleString()}
Created: {new Date(inv.createdAt).toLocaleString()} Expires: {new Date(inv.expiresAt).toLocaleString()}
</div>
</div>
<div className="flex items-center gap-2 shrink-0">

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState } from "react";
import { fetchClient } from "@/lib/api-client";

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useEffect, useState } from "react";
import { fetchClient } from "@/lib/api-client";
@@ -81,7 +81,7 @@ export default function RecentOrdersCard() {
</div>
</div>
<div className="mt-1 text-xs text-muted-foreground">
User: {o.userId} · Total: £{Number(o.total).toFixed(2)}
User: {o.userId} Total: £{Number(o.total).toFixed(2)}
</div>
{o.items && o.items.length > 0 && (
<ul className="mt-2 text-xs list-disc pl-4 text-muted-foreground">

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useEffect, useState } from "react";
import { fetchClient } from "@/lib/api-client";
import { Button } from "@/components/ui/button";

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useEffect, useState } from "react";
import { fetchClient } from "@/lib/api-client";
@@ -63,7 +63,7 @@ export default function VendorsCard() {
<div className="font-medium">{vendor.username}</div>
<div className="text-xs text-muted-foreground">
Created: {new Date(vendor.createdAt).toLocaleDateString()}
{vendor.lastLogin && ` · Last login: ${new Date(vendor.lastLogin).toLocaleDateString()}`}
{vendor.lastLogin && ` Last login: ${new Date(vendor.lastLogin).toLocaleDateString()}`}
</div>
</div>
<div className="flex items-center gap-2">

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState, useEffect, useRef } from "react";
@@ -68,4 +68,4 @@ export function AnimatedCounter({
: `${prefix}${displayValue.toLocaleString()}${suffix}`;
return <span>{formattedValue}</span>;
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { Package, Users, CreditCard } from "lucide-react";
import { AnimatedCounter } from "./animated-counter";
@@ -80,4 +80,4 @@ export function AnimatedStatsSection({ stats }: AnimatedStatsProps) {
</div>
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useEffect } from "react"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useEffect, useState } from "react"
import { isDecember } from "@/lib/utils/christmas"
@@ -39,30 +39,30 @@ export function ChristmasDecorations() {
animationDuration: `${flake.duration}s`,
}}
>
ÔØä
</div>
))}
{/* Twinkling stars in corners */}
<div className="absolute top-4 left-4 text-yellow-300 animate-twinkle text-lg">
Ô£¿
</div>
<div className="absolute top-4 right-4 text-yellow-300 animate-twinkle text-lg" style={{ animationDelay: '0.5s' }}>
Ô£¿
</div>
<div className="absolute bottom-4 left-4 text-yellow-300 animate-twinkle text-lg" style={{ animationDelay: '1s' }}>
Ô£¿
</div>
<div className="absolute bottom-4 right-4 text-yellow-300 animate-twinkle text-lg" style={{ animationDelay: '1.5s' }}>
Ô£¿
</div>
{/* Christmas tree emoji decorations */}
<div className="absolute top-8 left-1/4 text-green-500/40 animate-sparkle text-xl" style={{ animationDelay: '2s' }}>
🎄
­ƒÄä
</div>
<div className="absolute top-8 right-1/4 text-green-500/40 animate-sparkle text-xl" style={{ animationDelay: '3s' }}>
🎄
­ƒÄä
</div>
</div>
)

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import React, { useState, useEffect, useCallback, useRef } from "react";
import { Package, ShoppingBag, Info, ExternalLink, Loader2, AlertTriangle } from "lucide-react";
@@ -237,7 +237,7 @@ export default function BuyerOrderInfo({ buyerId, chatId }: BuyerOrderInfoProps)
<div className="p-3 border-b">
<h4 className="font-medium text-sm">Customer Orders</h4>
<p className="text-xs text-muted-foreground">
{orders.length} {orders.length === 1 ? 'order' : 'orders'} Total: {formatPrice(
{orders.length} {orders.length === 1 ? 'order' : 'orders'} ÔÇó Total: {formatPrice(
orders.reduce((sum, order) => sum + order.totalPrice, 0)
)}
</p>
@@ -279,11 +279,11 @@ export default function BuyerOrderInfo({ buyerId, chatId }: BuyerOrderInfoProps)
<div className="flex justify-between items-center text-xs text-muted-foreground pl-5">
<div className="flex gap-1">
<span>{order.products.length} {order.products.length === 1 ? 'product' : 'products'}</span>
<span>·</span>
<span></span>
<span>{formatPrice(order.totalPrice)}</span>
{isOrderUnderpaid(order) && (
<>
<span>·</span>
<span></span>
<span className="text-red-500">Underpaid</span>
</>
)}
@@ -299,4 +299,4 @@ export default function BuyerOrderInfo({ buyerId, chatId }: BuyerOrderInfoProps)
</Tooltip>
</TooltipProvider>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import React, { useState, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
@@ -832,4 +832,4 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
/>
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useRouter } from 'next/navigation';
@@ -420,4 +420,4 @@ export default function ChatTable() {
)}
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import React, { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
@@ -350,4 +350,4 @@ export default function NewChatForm() {
</CardContent>
</Card>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useState, useEffect } from "react"
import OrderStats from "./order-stats"
@@ -75,7 +75,7 @@ export default function Content({ username, orderStats }: ContentProps) {
{greeting}, {username}!
</h1>
<p className="text-muted-foreground mt-1 italic text-sm">
"{randomQuote.text}" <span className="font-medium">{randomQuote.author}</span>
"{randomQuote.text}" ÔÇö <span className="font-medium">{randomQuote.author}</span>
</p>
</div>

View File

@@ -1,4 +1,4 @@
import type React from "react"
import type React from "react"
import Layout from "../layout/layout"
export default function Dashboard({ children }: { children: React.ReactNode }) {

View File

@@ -1,4 +1,4 @@
import type { LucideIcon } from "lucide-react"
import type { LucideIcon } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface OrderStatsProps {

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { Skeleton } from "@/components/ui/skeleton";
import { Card } from "@/components/ui/card";
@@ -113,4 +113,4 @@ export default function PageLoading({
</Card>
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
'use client';
'use client';
import { useState, useEffect } from 'react';
import { z } from 'zod';
@@ -464,4 +464,4 @@ export default function EditPromotionForm({ promotion, onSuccess, onCancel }: Ed
</form>
</Form>
);
}
}

View File

@@ -1,4 +1,4 @@
'use client';
'use client';
import { useState, useEffect } from 'react';
import { z } from 'zod';
@@ -459,4 +459,4 @@ export default function NewPromotionForm({ onSuccess, onCancel }: NewPromotionFo
</form>
</Form>
);
}
}

View File

@@ -1,4 +1,4 @@
'use client';
'use client';
import { useState, useEffect, useRef } from 'react';
import { Check, ChevronDown, X, Search } from 'lucide-react';
@@ -248,4 +248,4 @@ export default function ProductSelector({
)}
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
'use client';
'use client';
import { useState, useEffect } from 'react';
import {
@@ -222,7 +222,7 @@ export default function PromotionDetailsModal({
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium">Current Usage</span>
<span className="text-sm text-muted-foreground">
{promotion.usageCount} / {promotion.maxUsage || ''}
{promotion.usageCount} / {promotion.maxUsage || 'Ôê×'}
</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
@@ -382,4 +382,4 @@ export default function PromotionDetailsModal({
</DialogContent>
</Dialog>
);
}
}

View File

@@ -1,4 +1,4 @@
'use client';
'use client';
import { useState, useEffect } from 'react';
import { Plus, Tag, RefreshCw, Trash, Edit, Check, X, Eye } from 'lucide-react';
@@ -184,7 +184,7 @@ export default function PromotionsList() {
: 'None'}
</TableCell>
<TableCell>
{promotion.usageCount} / {promotion.maxUsage || ''}
{promotion.usageCount} / {promotion.maxUsage || 'Ôê×'}
</TableCell>
<TableCell>{formatDate(promotion.endDate)}</TableCell>
<TableCell>
@@ -306,4 +306,4 @@ export default function PromotionsList() {
/>
</>
);
}
}

View File

@@ -1,4 +1,4 @@
import {
import {
Table,
TableBody,
TableCell,
@@ -59,4 +59,4 @@ export default function PromotionsPageSkeleton() {
</Card>
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import Link from "next/link";
import { Button } from "@/components/ui/button";
@@ -89,4 +89,4 @@ export function HomeNavbar() {
)}
</header>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { usePathname } from "next/navigation";
import KeepOnline from "@/components/KeepOnline";
@@ -14,4 +14,4 @@ const KeepOnlineWrapper = () => {
return <KeepOnline />;
};
export default KeepOnlineWrapper;
export default KeepOnlineWrapper;

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useState, useEffect } from "react"
import { useTheme } from "next-themes"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
@@ -65,4 +65,4 @@ export const NavItem: React.FC<NavItemProps> = ({ href, icon: Icon, children, on
{children}
</Link>
)
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useState } from "react"
import Link from "next/link"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import type { ThemeProviderProps } from "next-themes"

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState, useRef } from "react";
import { Button } from "@/components/ui/button";
@@ -350,4 +350,4 @@ __italic text__
</DialogContent>
</Dialog>
);
}
}

View File

@@ -1,4 +1,4 @@
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { ChevronLeft, ChevronRight, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useEffect } from "react";
@@ -96,4 +96,4 @@ export function ImageViewerModal({ isOpen, onClose, imageUrl, onNavigate }: Imag
</DialogContent>
</Dialog>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
@@ -65,7 +65,7 @@ export default function ImportProductsModal({ open, setOpen, onImportComplete }:
const [name] = productInfo.split("\n");
if (!name) continue;
const [category, subcategory] = productInfo.split("\n")[1].split("")[0].split("->")
const [category, subcategory] = productInfo.split("\n")[1].split("ÔÇó")[0].split("->")
.map(item => item.trim());
if (!category || !subcategory) continue;
@@ -193,4 +193,4 @@ export default function ImportProductsModal({ open, setOpen, onImportComplete }:
</DialogContent>
</Dialog>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState, useEffect } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
@@ -320,7 +320,7 @@ const ProductBasicInfo: React.FC<{
</div>
<div className="bg-background rounded-lg border border-border p-4">
<h3 className="text-sm font-medium mb-4">💰 Cost & Profit Tracking</h3>
<h3 className="text-sm font-medium mb-4">­ƒÆ Cost & Profit Tracking</h3>
<p className="text-xs text-muted-foreground mb-4">
Track your costs to automatically calculate profit margins and markup percentages.
</p>
@@ -474,4 +474,4 @@ const UnitTypeSelect: React.FC<{
</Button>
</div>
</div>
);
);

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
@@ -127,4 +127,4 @@ export default function ProductSelector({ selectedProducts, onSelectionChange }:
)}
</div>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useState, useEffect } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { ChangeEvent, FormEvent } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
@@ -9,7 +9,7 @@ import { ShippingData } from "@/lib/types";
interface ShippingModalProps {
open: boolean;
onClose: () => void;
onSave: (shippingData: ShippingData) => void; // Allow passing shippingData
onSave: (shippingData: ShippingData) => void; //  Allow passing shippingData
shippingData: ShippingData;
setShippingData: React.Dispatch<React.SetStateAction<ShippingData>>;
editing: boolean;

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { useEffect, useState } from "react"
import { isDecember } from "@/lib/utils/christmas"
@@ -45,7 +45,7 @@ export function SnowLoader({ className = "", count = 20 }: SnowLoaderProps) {
fontSize: `${flake.size}rem`,
}}
>
ÔØä
</div>
))}
</div>

View File

@@ -1,4 +1,4 @@
"use client";
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
@@ -27,4 +27,4 @@ export function ThemeSwitcher() {
)}
</Button>
);
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils/styles";

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"

View File

@@ -1,4 +1,4 @@
import * as React from "react";
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils/styles";
@@ -35,4 +35,4 @@ function Badge({ className, variant, ...props }: BadgeProps) {
);
}
export { Badge, badgeVariants };
export { Badge, badgeVariants };

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils/general"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import useEmblaCarousel, {

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as RechartsPrimitive from "recharts"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import { format, addDays, startOfDay, endOfDay, isSameDay, isWithinInterval, getMonth, getYear, setMonth, setYear } from "date-fns"
@@ -410,4 +410,4 @@ export function DateRangeDisplay({ dateRange }: { dateRange?: DateRange }) {
</span>
</div>
)
}
}

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils/styles";

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils/styles";

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"

View File

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

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils/styles";

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"

View File

@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from "react"
import { cn } from "@/lib/utils/styles";

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"

View File

@@ -1,4 +1,4 @@
"use client"
"use client"
// Inspired by react-hot-toast library
import * as React from "react"

View File

@@ -377,6 +377,99 @@ export const getCustomerDetails = async (userId: string): Promise<CustomerStats>
return clientFetch(`/customers/${userId}`);
};
/**
* Export orders by status to CSV
* @param status Order status to filter by
* @param storeId Optional store ID (uses authenticated user's store if not provided)
* @returns Promise that resolves when download is triggered
*/
export const exportOrdersToCSV = async (status: string, storeId?: string): Promise<void> => {
try {
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
const normalizedEndpoint = '/orders/export-csv';
// Handle API endpoint construction
let url;
if (apiUrl === '/api' || apiUrl.startsWith('/api')) {
url = `/api${normalizedEndpoint}`;
} else {
url = `${apiUrl}${normalizedEndpoint}`;
}
// Add query parameters
const params = new URLSearchParams({ status });
if (storeId) {
params.append('storeId', storeId);
}
url = `${url}?${params.toString()}`;
// Get auth token
const authToken = getAuthToken();
// Prepare headers
const headers: Record<string, string> = {};
if (authToken && authToken !== 'HTTP_ONLY_COOKIE') {
headers['Authorization'] = `Bearer ${authToken}`;
}
// Fetch the CSV file
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
headers,
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage = errorData.message || errorData.error || 'Failed to export orders';
throw new Error(errorMessage);
}
// Get the CSV content
const blob = await response.blob();
// Get filename from Content-Disposition header or generate one
const contentDisposition = response.headers.get('Content-Disposition');
let filename = `orders_${status}_${new Date().toISOString().split('T')[0]}.csv`;
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/);
if (filenameMatch) {
filename = filenameMatch[1];
}
}
// Create download link and trigger download
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Error exporting orders to CSV:', error);
const message = error instanceof Error ? error.message : 'Failed to export orders';
// Show error toast
if (typeof window !== 'undefined') {
if (toast?.title && toast?.description) {
toast({
title: 'Export Failed',
description: message,
variant: 'destructive',
});
} else if (typeof toast?.error === 'function') {
toast.error(message);
} else {
console.error('CSV export error:', message);
}
}
throw error;
}
};
/**
* Enhanced client-side fetch function with caching and automatic invalidation
*/

View File

@@ -10,6 +10,9 @@ export {
getCustomers,
getCustomerDetails,
// Orders API
exportOrdersToCSV,
// Types
type CustomerStats,
type CustomerResponse,

View File

@@ -1,4 +1,4 @@
{
"commitHash": "e7fcfd6",
"buildTime": "2025-12-08T01:01:03.674Z"
"commitHash": "07dcaf5",
"buildTime": "2025-12-15T17:54:38.040Z"
}