This commit is contained in:
g
2025-02-07 21:33:13 +00:00
parent 8900bbcc76
commit 891f57d729
50 changed files with 165 additions and 444 deletions

View File

@@ -1,3 +1,4 @@
import dataService from '@/lib/data-service';
"use client";
import { useState } from "react";
@@ -20,7 +21,7 @@ export default function LoginPage() {
e.preventDefault();
setError("");
const res = await fetch("https://internal-api.inboxi.ng/api/auth/login", {
const res = await dataService.fetchData("https://internal-api.inboxi.ng/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),

View File

@@ -1,3 +1,4 @@
import dataService from '@/lib/data-service';
"use client";
import { useState } from "react";
@@ -21,7 +22,7 @@ export default function RegisterPage() {
setError("");
setLoading(true);
const res = await fetch("https://internal-api.inboxi.ng/api/auth/register", {
const res = await dataService.fetchData("https://internal-api.inboxi.ng/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password, invitationCode }),

View File

@@ -1,8 +1,10 @@
"use client";
import { fetchData } from '@/lib/data-service';
import { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import Layout from "@/components/kokonutui/layout";
import Layout from "@/components/layout/layout";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -55,10 +57,34 @@ export default function OrderDetailsPage() {
const params = useParams();
const orderId = params?.id;
const fetchProductNames = async (
productIds: string[],
authToken: string
): Promise<Record<string, string>> => {
const productNamesMap: Record<string, string> = {};
try {
const promises = productIds.map((id) =>
fetchData(`${process.env.NEXT_PUBLIC_API_URL}/products/${id}`, {
method: "GET",
headers: { Authorization: `Bearer ${authToken}` },
})
);
const responses = await Promise.all(promises);
const results = await Promise.all(responses.map((res) => res));
results.forEach((product, index) => {
productNamesMap[productIds[index]] = product.name || "Unknown Product";
});
} catch (err) {
console.error("Failed to fetch product names:", err);
}
return productNamesMap;
};
const handleMarkAsPaid = async () => {
try {
const authToken = document.cookie.split("Authorization=")[1];
const response = await fetch(
const response = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
{
method: "PUT",
@@ -70,7 +96,7 @@ export default function OrderDetailsPage() {
}
);
if (response.ok) {
if (response && response.message === "Order status updated successfully") {
setIsPaid(true); // Update isPaid state
setOrder((prevOrder) => (prevOrder ? { ...prevOrder, status: "paid" } : null)); // Update order status
console.log("Order marked as paid successfully.");
@@ -92,7 +118,7 @@ export default function OrderDetailsPage() {
const authToken = document.cookie.split("Authorization=")[1];
const res = await fetch(
const res = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
{
method: "GET",
@@ -100,9 +126,9 @@ export default function OrderDetailsPage() {
}
);
if (!res.ok) throw new Error("Failed to fetch order details");
if (!res) throw new Error("Failed to fetch order details");
const data: Order = await res.json();
const data: Order = await res;
setOrder(data);
const productIds = data.products.map((product) => product.productId);
@@ -120,32 +146,8 @@ export default function OrderDetailsPage() {
}
};
const fetchProductNames = async (
productIds: string[],
authToken: string
): Promise<Record<string, string>> => {
const productNamesMap: Record<string, string> = {};
try {
const promises = productIds.map((id) =>
fetch(`${process.env.NEXT_PUBLIC_API_URL}/products/${id}`, {
method: "GET",
headers: { Authorization: `Bearer ${authToken}` },
})
);
const responses = await Promise.all(promises);
const results = await Promise.all(responses.map((res) => res.json()));
results.forEach((product, index) => {
productNamesMap[productIds[index]] = product.name || "Unknown Product";
});
} catch (err) {
console.error("Failed to fetch product names:", err);
}
return productNamesMap;
};
fetchOrderDetails();
}, [orderId]);
}, [orderId]);
const handleAddTracking = async () => {
if (!trackingNumber) return;
@@ -153,7 +155,7 @@ export default function OrderDetailsPage() {
try {
const authToken = document.cookie.split("Authorization=")[1];
const res = await fetch(
const res = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/tracking`,
{
method: "PUT",

View File

@@ -2,9 +2,9 @@
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import Dashboard from "@/components/kokonutui/dashboard";
import Dashboard from "@/components/dashboard/dashboard";
import { Package } from "lucide-react";
import OrderTable from "@/components/order-table";
import OrderTable from "@/components/tables/order-table";
export default function OrdersPage() {
const router = useRouter();

View File

@@ -1,6 +1,6 @@
import Dashboard from "@/components/kokonutui/dashboard";
import Content from "@/components/kokonutui/content";
import { fetchServer } from "@/lib/server-utils"
import Dashboard from "@/components/dashboard/dashboard";
import Content from "@/components/dashboard/content";
import { fetchServer } from '@/lib/server-service';
// ✅ Corrected Vendor Type
interface Vendor {

View File

@@ -2,7 +2,7 @@
import { useState, useEffect, ChangeEvent } from "react";
import { useRouter } from "next/navigation";
import Layout from "@/components/kokonutui/layout";
import Layout from "@/components/layout/layout";
import { Button } from "@/components/ui/button";
import { Product } from "@/models/products";
import { Plus } from "lucide-react";
@@ -11,8 +11,8 @@ import {
saveProductData,
deleteProductData,
} from "@/lib/productData";
import { ProductModal } from "@/components/product-modal";
import ProductTable from "@/components/product-table";
import { ProductModal } from "@/components/modals/product-modal";
import ProductTable from "@/components/tables/product-table";
export default function ProductsPage() {
const router = useRouter();
@@ -34,14 +34,14 @@ export default function ProductsPage() {
// Fetch products and categories
useEffect(() => {
const authToken = document.cookie
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
if (!authToken) {
router.push("/login");
return;
}
if (!authToken) {
router.push("/login");
return;
}
const fetchDataAsync = async () => {
try {
@@ -56,7 +56,15 @@ export default function ProductsPage() {
),
]);
setProducts(fetchedProducts);
console.log("Fetched Products:", fetchedProducts);
// Ensure all products have tieredPricing
const processedProducts = fetchedProducts.map((product: Product) => ({
...product,
tieredPricing: product.tieredPricing || [{ minQuantity: 1, pricePerUnit: 0 }],
}));
setProducts(processedProducts);
setCategories(fetchedCategories);
} catch (error) {
console.error("Error loading data:", error);
@@ -150,10 +158,12 @@ export default function ProductsPage() {
const handleEditProduct = (product: Product) => {
setProductData({
...product,
tieredPricing: product.tieredPricing.map(tier => ({
minQuantity: tier.minQuantity,
pricePerUnit: tier.pricePerUnit
})),
tieredPricing: product.tieredPricing
? product.tieredPricing.map(tier => ({
minQuantity: tier.minQuantity,
pricePerUnit: tier.pricePerUnit
}))
: [{ minQuantity: 1, pricePerUnit: 0 }], // Fallback if undefined
});
setEditing(true);
setModalOpen(true);

View File

@@ -2,10 +2,10 @@
import { useState, useEffect, ChangeEvent } from "react";
import { useRouter } from "next/navigation";
import Layout from "@/components/kokonutui/layout";
import Layout from "@/components/layout/layout";
import { Edit, Plus, Trash } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ShippingModal } from "@/components/shipping-modal";
import { ShippingModal } from "@/components/modals/shipping-modal";
import { Skeleton } from "@/components/ui/skeleton";
import {
fetchShippingMethods,
@@ -16,7 +16,7 @@ import {
import { ShippingMethod, ShippingData } from "@/lib/types";
import { ShippingTable } from "@/components/shipping-table";
import { ShippingTable } from "@/components/tables/shipping-table";
export default function ShippingPage() {
const [shippingMethods, setShippingMethods] = useState<ShippingMethod[]>([]);
@@ -54,6 +54,8 @@ export default function ShippingPage() {
})
);
console.log("Fetched Shipping Methods:", sanitizedMethods);
setShippingMethods(sanitizedMethods);
} catch (error) {
console.error("Error loading shipping options:", error);

View File

@@ -2,14 +2,14 @@
import { useState, useEffect, ChangeEvent } from "react";
import { useRouter } from "next/navigation";
import Layout from "@/components/kokonutui/layout";
import Layout from "@/components/layout/layout";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Save, Send, Key, MessageSquare, Shield } from "lucide-react";
import { apiRequest } from "@/lib/storeHelper";
import { toast, Toaster } from "sonner";
import BroadcastDialog from "@/components/broadcast-dialog";
import BroadcastDialog from "@/components/modals/broadcast-dialog";
// ✅ Define the Storefront Type
interface Storefront {

View File

@@ -1,6 +1,6 @@
import { Inter } from "next/font/google"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { ThemeProvider } from "@/components/layout/theme-provider";
const inter = Inter({ subsets: ["latin"] })

View File

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

View File

@@ -1,5 +1,7 @@
"use client";
import { fetchData } from '@/lib/data-service';
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -32,14 +34,14 @@ const NavItem = ({ href, icon: Icon, children, onClick }: NavItemProps) => (
</Link>
);
export default function Sidebar() {
function Sidebar() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const router = useRouter();
const handleLogout = async () => {
try {
const authToken = document.cookie.split("authToken=")[1];
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/logout`, {
const res = await fetchData(`${process.env.NEXT_PUBLIC_API_URL}/logout`, {
method: "POST",
headers: { Authorization: `Bearer ${authToken}` },
credentials: "include",
@@ -138,3 +140,5 @@ export default function Sidebar() {
</>
);
}
export default Sidebar;

View File

@@ -28,7 +28,7 @@ import {
Truck,
} from "lucide-react";
import Link from "next/link";
import { fetchClient } from "@/lib/client-utils";
import { clientFetch } from '@/lib/client-utils';
import { toast } from "sonner";
import { Checkbox } from "@/components/ui/checkbox";
@@ -59,7 +59,7 @@ export default function OrderTable() {
const fetchOrders = useCallback(async () => {
try {
setLoading(true);
const data = await fetchClient<{ orders: Order[] }>("/orders");
const data = await clientFetch("/orders");
setOrders(data.orders || []);
} catch (error) {
toast.error("Failed to fetch orders");
@@ -132,7 +132,7 @@ export default function OrderTable() {
}
try {
await fetchClient("/orders/mark-shipped", {
await clientFetch("/orders/mark-shipped", {
method: "POST",
body: JSON.stringify({ orderIds: Array.from(selectedOrders) })
});

View File

@@ -1,7 +0,0 @@
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
const AspectRatio = AspectRatioPrimitive.Root
export { AspectRatio }

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cn } from "@/lib/styles";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",

View File

@@ -2,7 +2,7 @@ import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { cn } from '@/lib/styles';
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",

View File

@@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/lib/styles";
const Card = React.forwardRef<
HTMLDivElement,

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from '@/lib/styles';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from '@/lib/styles';
const Dialog = DialogPrimitive.Root

View File

@@ -1,29 +0,0 @@
"use client"
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root
const HoverCardTrigger = HoverCardPrimitive.Trigger
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
export { HoverCard, HoverCardTrigger, HoverCardContent }

View File

@@ -1,71 +0,0 @@
"use client"
import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { Dot } from "lucide-react"
import { cn } from "@/lib/utils"
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
))
InputOTP.displayName = "InputOTP"
const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} />
))
InputOTPGroup.displayName = "InputOTPGroup"
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
return (
<div
ref={ref}
className={cn(
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-2 ring-ring ring-offset-background",
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
)
})
InputOTPSlot.displayName = "InputOTPSlot"
const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
))
InputOTPSeparator.displayName = "InputOTPSeparator"
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }

View File

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

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { cn } from "@/lib/styles";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"

View File

@@ -4,7 +4,7 @@ import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "@/lib/utils"
import { cn } from '@/lib/styles';
const Select = SelectPrimitive.Root

View File

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

View File

@@ -1,31 +0,0 @@
"use client"
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
)
}
export { Toaster }

View File

@@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from '@/lib/styles';
const Table = React.forwardRef<
HTMLTableElement,

View File

@@ -1,6 +1,6 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from '@/lib/styles';
const Textarea = React.forwardRef<
HTMLTextAreaElement,

View File

@@ -1,35 +0,0 @@
"use client"
import { useToast } from "@/hooks/use-toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}

View File

@@ -1,19 +0,0 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}

View File

@@ -1,46 +1,32 @@
/**
* Client-side API utilities with authentication handling
* Simple client-side fetch function for making API calls with Authorization header.
*/
const getAuthToken = (): string | null => {
const token = document.cookie
.split('; ')
.find(row => row.startsWith('Authorization='))
?.split('=')[1];
return token || null; // Return null instead of throwing an error
};
export const fetchClient = async <T = unknown>(
endpoint: string,
options: RequestInit = {}
): Promise<T> => {
export async function clientFetch(url: string, options: RequestInit = {}): Promise<any> {
try {
const authToken = getAuthToken();
if (!authToken) {
console.warn("No authentication token found. Redirecting to login...");
window.location.href = "/login";
return Promise.reject("Unauthorized");
}
const authToken = document.cookie
.split('; ')
.find(row => row.startsWith('Authorization='))
?.split('=')[1] || localStorage.getItem('Authorization');
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
...options.headers,
},
credentials: 'include',
});
console.log('authToken', authToken);
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.message || `Request failed: ${res.statusText}`);
}
// Merge Authorization header if token is found
const headers = {
'Content-Type': 'application/json',
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
...options.headers,
};
return res.json();
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, {
...options,
headers,
});
if (!res.ok) throw new Error(`Request failed: ${res.statusText}`);
return res.json();
} catch (error) {
console.error(`API request to ${endpoint} failed:`, error);
throw error;
console.error(`Client fetch error at ${url}:`, error);
throw error;
}
};
}

View File

@@ -1,31 +1,13 @@
import { fetchClient } from './client-utils';
import type { Product, ShippingMethod, ApiResponse } from './types';
export const ProductService = {
getAll: async (): Promise<ApiResponse<Product[]>> =>
fetchClient('/products'),
create: async (product: Omit<Product, '_id'>): Promise<ApiResponse<Product>> =>
fetchClient('/products', { method: 'POST', body: JSON.stringify(product) }),
update: async (id: string, product: Partial<Product>): Promise<ApiResponse<Product>> =>
fetchClient(`/products/${id}`, { method: 'PUT', body: JSON.stringify(product) }),
delete: async (id: string): Promise<ApiResponse<void>> =>
fetchClient(`/products/${id}`, { method: 'DELETE' })
};
// Shipping Operations
export const ShippingService = {
getAll: async (): Promise<ApiResponse<ShippingMethod[]>> =>
fetchClient('/shipping-options'),
create: async (method: Omit<ShippingMethod, '_id'>): Promise<ApiResponse<ShippingMethod>> =>
fetchClient('/shipping-options', { method: 'POST', body: JSON.stringify(method) }),
update: async (id: string, method: Partial<ShippingMethod>): Promise<ApiResponse<ShippingMethod>> =>
fetchClient(`/shipping-options/${id}`, { method: 'PUT', body: JSON.stringify(method) }),
delete: async (id: string): Promise<ApiResponse<void>> =>
fetchClient(`/shipping-options/${id}`, { method: 'DELETE' })
};
/**
* Client-side fetch function for API requests.
*/
export async function fetchData(url: string, options: RequestInit = {}): Promise<any> {
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`Request failed: ${res.statusText}`);
return res.json();
} catch (error) {
console.error(`Fetch error at ${url}:`, error);
throw error;
}
}

View File

@@ -1,18 +0,0 @@
export const fetchData = async (url: string, authToken: string) => {
try {
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${authToken}`,
},
credentials: "include",
});
if (!response.ok) throw new Error("Failed to fetch data");
return await response.json();
} catch (error) {
console.error("Error fetching data:", error);
throw error;
}
};

View File

@@ -1,13 +1,11 @@
import { fetchData } from '@/lib/data-service';
export const fetchProductData = async (url: string, authToken: string) => {
try {
const response = await fetch(url, {
return await fetchData(url, {
headers: { Authorization: `Bearer ${authToken}` },
credentials: "include",
});
if (!response.ok) {
throw new Error("Failed to fetch product data");
}
return await response.json();
} catch (error) {
console.error("Error fetching product data:", error);
throw error;
@@ -21,7 +19,7 @@ export const saveProductData = async (
method: "POST" | "PUT" = "POST"
) => {
try {
const response = await fetch(url, {
return await fetchData(url, {
method,
headers: {
Authorization: `Bearer ${authToken}`,
@@ -30,10 +28,6 @@ export const saveProductData = async (
credentials: "include",
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error("Failed to save product data");
}
return await response.json();
} catch (error) {
console.error("Error saving product data:", error);
throw error;
@@ -42,7 +36,7 @@ export const saveProductData = async (
export const deleteProductData = async (url: string, authToken: string) => {
try {
const response = await fetch(url, {
return await fetchData(url, {
method: "DELETE",
headers: {
Authorization: `Bearer ${authToken}`,
@@ -50,11 +44,6 @@ export const deleteProductData = async (url: string, authToken: string) => {
},
credentials: "include",
});
if (!response.ok) {
throw new Error("Failed to delete product data");
}
return await response.json();
} catch (error) {
console.error("Error deleting product data:", error);
throw error;

View File

@@ -2,14 +2,14 @@ import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
/**
* Server-side fetch wrapper with auth handling
* Server-side fetch wrapper with authentication.
*/
export const fetchServer = async <T = unknown>(
export async function fetchServer<T = unknown>(
endpoint: string,
options: RequestInit = {}
): Promise<T> => {
): Promise<T> {
const cookieStore = cookies();
const authToken = await cookieStore.get('Authorization')?.value;
const authToken = cookieStore.get('Authorization')?.value;
if (!authToken) redirect('/login');
@@ -32,4 +32,4 @@ export const fetchServer = async <T = unknown>(
console.error(`Server request to ${endpoint} failed:`, error);
throw error;
}
};
}

View File

@@ -1,6 +1,8 @@
import { fetchData } from '@/lib/data-service';
export const fetchShippingMethods = async (authToken: string) => {
try {
const res = await fetch(
const res = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options`,
{
headers: {
@@ -11,8 +13,10 @@ export const fetchShippingMethods = async (authToken: string) => {
}
);
if (!res.ok) throw new Error("Failed to fetch shipping options");
return await res.json();
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;
@@ -30,7 +34,7 @@ export const addShippingMethod = async (
newShipping: Omit<ShippingMethod, "_id">
): Promise<ShippingMethod[]> => {
try {
const res = await fetch(
const res = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options`,
{
method: "POST",
@@ -61,7 +65,7 @@ export const addShippingMethod = async (
export const deleteShippingMethod = async (authToken: string, id: string) => {
try {
const res = await fetch(
const res = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`,
{
method: "DELETE",
@@ -71,7 +75,6 @@ export const deleteShippingMethod = async (authToken: string, id: string) => {
);
if (!res.ok) throw new Error("Failed to delete shipping method");
// Since there is no content, just return success status.
return { success: res.status === 204 };
} catch (error) {
console.error("Error deleting shipping method:", error);
@@ -85,7 +88,7 @@ export const updateShippingMethod = async (
updatedShipping: any
) => {
try {
const res = await fetch(
const res = await fetchData(
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`,
{
method: "PUT",
@@ -98,8 +101,8 @@ export const updateShippingMethod = async (
}
);
if (!res.ok) throw new Error("Failed to update shipping method");
return await res.json();
if (!res) throw new Error("Failed to update shipping method");
return res
} catch (error) {
console.error("Error updating shipping method:", error);
throw error;

View File

@@ -1,3 +1,5 @@
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") {
@@ -10,7 +12,6 @@ export const apiRequest = async <T = any>(endpoint: string, method: string = "GE
?.split("=")[1];
if (!authToken){
// go to /login
document.location.href = "/login";
throw new Error("No authentication token found");
}
@@ -32,16 +33,16 @@ export const apiRequest = async <T = any>(endpoint: string, method: string = "GE
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 fetch(`${API_URL}${endpoint}`, options);
const res = await fetchData(`${API_URL}${endpoint}`, options);
if (!res.ok) {
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 JSON response
return await res.json();
return res;
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`🚨 API Request Error: ${error.message}`);

View File

@@ -1,50 +0,0 @@
module.exports = {
darkMode: ["class"],
content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
plugins: [require("tailwindcss-animate")],
}