Refactor
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import dataService from '@/lib/data-service';
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -20,7 +21,7 @@ export default function LoginPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError("");
|
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",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username, password }),
|
body: JSON.stringify({ username, password }),
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dataService from '@/lib/data-service';
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -21,7 +22,7 @@ export default function RegisterPage() {
|
|||||||
setError("");
|
setError("");
|
||||||
setLoading(true);
|
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",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ username, password, invitationCode }),
|
body: JSON.stringify({ username, password, invitationCode }),
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { fetchData } from '@/lib/data-service';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
import Layout from "@/components/kokonutui/layout";
|
import Layout from "@/components/layout/layout";
|
||||||
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 { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -55,10 +57,34 @@ export default function OrderDetailsPage() {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const orderId = params?.id;
|
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 () => {
|
const handleMarkAsPaid = async () => {
|
||||||
try {
|
try {
|
||||||
const authToken = document.cookie.split("Authorization=")[1];
|
const authToken = document.cookie.split("Authorization=")[1];
|
||||||
const response = await fetch(
|
const response = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
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
|
setIsPaid(true); // Update isPaid state
|
||||||
setOrder((prevOrder) => (prevOrder ? { ...prevOrder, status: "paid" } : null)); // Update order status
|
setOrder((prevOrder) => (prevOrder ? { ...prevOrder, status: "paid" } : null)); // Update order status
|
||||||
console.log("Order marked as paid successfully.");
|
console.log("Order marked as paid successfully.");
|
||||||
@@ -92,7 +118,7 @@ export default function OrderDetailsPage() {
|
|||||||
|
|
||||||
const authToken = document.cookie.split("Authorization=")[1];
|
const authToken = document.cookie.split("Authorization=")[1];
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
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);
|
setOrder(data);
|
||||||
|
|
||||||
const productIds = data.products.map((product) => product.productId);
|
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();
|
fetchOrderDetails();
|
||||||
}, [orderId]);
|
}, [orderId]);
|
||||||
|
|
||||||
const handleAddTracking = async () => {
|
const handleAddTracking = async () => {
|
||||||
if (!trackingNumber) return;
|
if (!trackingNumber) return;
|
||||||
@@ -153,7 +155,7 @@ export default function OrderDetailsPage() {
|
|||||||
try {
|
try {
|
||||||
const authToken = document.cookie.split("Authorization=")[1];
|
const authToken = document.cookie.split("Authorization=")[1];
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/tracking`,
|
`${process.env.NEXT_PUBLIC_API_URL}/orders/${orderId}/tracking`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Dashboard from "@/components/kokonutui/dashboard";
|
import Dashboard from "@/components/dashboard/dashboard";
|
||||||
import { Package } from "lucide-react";
|
import { Package } from "lucide-react";
|
||||||
import OrderTable from "@/components/order-table";
|
import OrderTable from "@/components/tables/order-table";
|
||||||
|
|
||||||
export default function OrdersPage() {
|
export default function OrdersPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Dashboard from "@/components/kokonutui/dashboard";
|
import Dashboard from "@/components/dashboard/dashboard";
|
||||||
import Content from "@/components/kokonutui/content";
|
import Content from "@/components/dashboard/content";
|
||||||
import { fetchServer } from "@/lib/server-utils"
|
import { fetchServer } from '@/lib/server-service';
|
||||||
|
|
||||||
// ✅ Corrected Vendor Type
|
// ✅ Corrected Vendor Type
|
||||||
interface Vendor {
|
interface Vendor {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, ChangeEvent } from "react";
|
import { useState, useEffect, ChangeEvent } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Layout from "@/components/kokonutui/layout";
|
import Layout from "@/components/layout/layout";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Product } from "@/models/products";
|
import { Product } from "@/models/products";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
@@ -11,8 +11,8 @@ import {
|
|||||||
saveProductData,
|
saveProductData,
|
||||||
deleteProductData,
|
deleteProductData,
|
||||||
} from "@/lib/productData";
|
} from "@/lib/productData";
|
||||||
import { ProductModal } from "@/components/product-modal";
|
import { ProductModal } from "@/components/modals/product-modal";
|
||||||
import ProductTable from "@/components/product-table";
|
import ProductTable from "@/components/tables/product-table";
|
||||||
|
|
||||||
export default function ProductsPage() {
|
export default function ProductsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -34,14 +34,14 @@ export default function ProductsPage() {
|
|||||||
// Fetch products and categories
|
// Fetch products and categories
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const authToken = document.cookie
|
const authToken = document.cookie
|
||||||
.split("; ")
|
.split("; ")
|
||||||
.find((row) => row.startsWith("Authorization="))
|
.find((row) => row.startsWith("Authorization="))
|
||||||
?.split("=")[1];
|
?.split("=")[1];
|
||||||
|
|
||||||
if (!authToken) {
|
if (!authToken) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchDataAsync = async () => {
|
const fetchDataAsync = async () => {
|
||||||
try {
|
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);
|
setCategories(fetchedCategories);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading data:", error);
|
console.error("Error loading data:", error);
|
||||||
@@ -150,10 +158,12 @@ export default function ProductsPage() {
|
|||||||
const handleEditProduct = (product: Product) => {
|
const handleEditProduct = (product: Product) => {
|
||||||
setProductData({
|
setProductData({
|
||||||
...product,
|
...product,
|
||||||
tieredPricing: product.tieredPricing.map(tier => ({
|
tieredPricing: product.tieredPricing
|
||||||
minQuantity: tier.minQuantity,
|
? product.tieredPricing.map(tier => ({
|
||||||
pricePerUnit: tier.pricePerUnit
|
minQuantity: tier.minQuantity,
|
||||||
})),
|
pricePerUnit: tier.pricePerUnit
|
||||||
|
}))
|
||||||
|
: [{ minQuantity: 1, pricePerUnit: 0 }], // Fallback if undefined
|
||||||
});
|
});
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
import { useState, useEffect, ChangeEvent } from "react";
|
import { useState, useEffect, ChangeEvent } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
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 { Edit, Plus, Trash } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { Skeleton } from "@/components/ui/skeleton";
|
||||||
import {
|
import {
|
||||||
fetchShippingMethods,
|
fetchShippingMethods,
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
|
|
||||||
import { ShippingMethod, ShippingData } from "@/lib/types";
|
import { ShippingMethod, ShippingData } from "@/lib/types";
|
||||||
|
|
||||||
import { ShippingTable } from "@/components/shipping-table";
|
import { ShippingTable } from "@/components/tables/shipping-table";
|
||||||
|
|
||||||
export default function ShippingPage() {
|
export default function ShippingPage() {
|
||||||
const [shippingMethods, setShippingMethods] = useState<ShippingMethod[]>([]);
|
const [shippingMethods, setShippingMethods] = useState<ShippingMethod[]>([]);
|
||||||
@@ -54,6 +54,8 @@ export default function ShippingPage() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log("Fetched Shipping Methods:", sanitizedMethods);
|
||||||
|
|
||||||
setShippingMethods(sanitizedMethods);
|
setShippingMethods(sanitizedMethods);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading shipping options:", error);
|
console.error("Error loading shipping options:", error);
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
import { useState, useEffect, ChangeEvent } from "react";
|
import { useState, useEffect, ChangeEvent } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Layout from "@/components/kokonutui/layout";
|
import Layout from "@/components/layout/layout";
|
||||||
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 { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Save, Send, Key, MessageSquare, Shield } from "lucide-react";
|
import { Save, Send, Key, MessageSquare, Shield } from "lucide-react";
|
||||||
import { apiRequest } from "@/lib/storeHelper";
|
import { apiRequest } from "@/lib/storeHelper";
|
||||||
import { toast, Toaster } from "sonner";
|
import { toast, Toaster } from "sonner";
|
||||||
import BroadcastDialog from "@/components/broadcast-dialog";
|
import BroadcastDialog from "@/components/modals/broadcast-dialog";
|
||||||
|
|
||||||
// ✅ Define the Storefront Type
|
// ✅ Define the Storefront Type
|
||||||
interface Storefront {
|
interface Storefront {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inter } from "next/font/google"
|
import { Inter } from "next/font/google"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
import { ThemeProvider } from "@/components/theme-provider"
|
import { ThemeProvider } from "@/components/layout/theme-provider";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] })
|
const inter = Inter({ subsets: ["latin"] })
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type React from "react"
|
import type React from "react"
|
||||||
import Layout from "./layout"
|
import Layout from "../layout/layout"
|
||||||
|
|
||||||
export default function Dashboard({ children }: { children: React.ReactNode }) {
|
export default function Dashboard({ children }: { children: React.ReactNode }) {
|
||||||
return <Layout>{children}</Layout>
|
return <Layout>{children}</Layout>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { fetchData } from '@/lib/data-service';
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -32,14 +34,14 @@ const NavItem = ({ href, icon: Icon, children, onClick }: NavItemProps) => (
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function Sidebar() {
|
function Sidebar() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
const authToken = document.cookie.split("authToken=")[1];
|
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",
|
method: "POST",
|
||||||
headers: { Authorization: `Bearer ${authToken}` },
|
headers: { Authorization: `Bearer ${authToken}` },
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
@@ -138,3 +140,5 @@ export default function Sidebar() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Sidebar;
|
||||||
@@ -28,7 +28,7 @@ import {
|
|||||||
Truck,
|
Truck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { fetchClient } from "@/lib/client-utils";
|
import { clientFetch } from '@/lib/client-utils';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export default function OrderTable() {
|
|||||||
const fetchOrders = useCallback(async () => {
|
const fetchOrders = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await fetchClient<{ orders: Order[] }>("/orders");
|
const data = await clientFetch("/orders");
|
||||||
setOrders(data.orders || []);
|
setOrders(data.orders || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("Failed to fetch orders");
|
toast.error("Failed to fetch orders");
|
||||||
@@ -132,7 +132,7 @@ export default function OrderTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchClient("/orders/mark-shipped", {
|
await clientFetch("/orders/mark-shipped", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ orderIds: Array.from(selectedOrders) })
|
body: JSON.stringify({ orderIds: Array.from(selectedOrders) })
|
||||||
});
|
});
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
|
|
||||||
|
|
||||||
const AspectRatio = AspectRatioPrimitive.Root
|
|
||||||
|
|
||||||
export { AspectRatio }
|
|
||||||
@@ -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/utils";
|
import { cn } from "@/lib/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 { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/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",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/styles";
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
const Card = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
|
|||||||
@@ -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/utils"
|
import { cn } from '@/lib/styles';
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<
|
const Checkbox = React.forwardRef<
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
React.ElementRef<typeof CheckboxPrimitive.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/utils"
|
import { cn } from '@/lib/styles';
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
@@ -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 }
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/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/utils"
|
import { cn } from "@/lib/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 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/utils"
|
import { cn } from '@/lib/styles';
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/styles"
|
||||||
|
|
||||||
function Skeleton({
|
function Skeleton({
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/styles';
|
||||||
|
|
||||||
const Table = React.forwardRef<
|
const Table = React.forwardRef<
|
||||||
HTMLTableElement,
|
HTMLTableElement,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from '@/lib/styles';
|
||||||
|
|
||||||
const Textarea = React.forwardRef<
|
const Textarea = React.forwardRef<
|
||||||
HTMLTextAreaElement,
|
HTMLTextAreaElement,
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,32 @@
|
|||||||
/**
|
/**
|
||||||
* Client-side API utilities with authentication handling
|
* Simple client-side fetch function for making API calls with Authorization header.
|
||||||
*/
|
*/
|
||||||
|
export async function clientFetch(url: string, options: RequestInit = {}): Promise<any> {
|
||||||
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> => {
|
|
||||||
try {
|
try {
|
||||||
const authToken = getAuthToken();
|
const authToken = document.cookie
|
||||||
if (!authToken) {
|
.split('; ')
|
||||||
console.warn("No authentication token found. Redirecting to login...");
|
.find(row => row.startsWith('Authorization='))
|
||||||
window.location.href = "/login";
|
?.split('=')[1] || localStorage.getItem('Authorization');
|
||||||
return Promise.reject("Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${endpoint}`, {
|
console.log('authToken', authToken);
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${authToken}`,
|
|
||||||
...options.headers,
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
// Merge Authorization header if token is found
|
||||||
const errorData = await res.json();
|
const headers = {
|
||||||
throw new Error(errorData.message || `Request failed: ${res.statusText}`);
|
'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) {
|
} catch (error) {
|
||||||
console.error(`API request to ${endpoint} failed:`, error);
|
console.error(`Client fetch error at ${url}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
@@ -1,31 +1,13 @@
|
|||||||
import { fetchClient } from './client-utils';
|
/**
|
||||||
import type { Product, ShippingMethod, ApiResponse } from './types';
|
* Client-side fetch function for API requests.
|
||||||
|
*/
|
||||||
export const ProductService = {
|
export async function fetchData(url: string, options: RequestInit = {}): Promise<any> {
|
||||||
getAll: async (): Promise<ApiResponse<Product[]>> =>
|
try {
|
||||||
fetchClient('/products'),
|
const res = await fetch(url, options);
|
||||||
|
if (!res.ok) throw new Error(`Request failed: ${res.statusText}`);
|
||||||
create: async (product: Omit<Product, '_id'>): Promise<ApiResponse<Product>> =>
|
return res.json();
|
||||||
fetchClient('/products', { method: 'POST', body: JSON.stringify(product) }),
|
} catch (error) {
|
||||||
|
console.error(`Fetch error at ${url}:`, error);
|
||||||
update: async (id: string, product: Partial<Product>): Promise<ApiResponse<Product>> =>
|
throw error;
|
||||||
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' })
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
|
import { fetchData } from '@/lib/data-service';
|
||||||
|
|
||||||
export const fetchProductData = async (url: string, authToken: string) => {
|
export const fetchProductData = async (url: string, authToken: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
return await fetchData(url, {
|
||||||
headers: { Authorization: `Bearer ${authToken}` },
|
headers: { Authorization: `Bearer ${authToken}` },
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to fetch product data");
|
|
||||||
}
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching product data:", error);
|
console.error("Error fetching product data:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -21,7 +19,7 @@ export const saveProductData = async (
|
|||||||
method: "POST" | "PUT" = "POST"
|
method: "POST" | "PUT" = "POST"
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
return await fetchData(url, {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${authToken}`,
|
Authorization: `Bearer ${authToken}`,
|
||||||
@@ -30,10 +28,6 @@ export const saveProductData = async (
|
|||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to save product data");
|
|
||||||
}
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving product data:", error);
|
console.error("Error saving product data:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -42,7 +36,7 @@ export const saveProductData = async (
|
|||||||
|
|
||||||
export const deleteProductData = async (url: string, authToken: string) => {
|
export const deleteProductData = async (url: string, authToken: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
return await fetchData(url, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${authToken}`,
|
Authorization: `Bearer ${authToken}`,
|
||||||
@@ -50,11 +44,6 @@ export const deleteProductData = async (url: string, authToken: string) => {
|
|||||||
},
|
},
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Failed to delete product data");
|
|
||||||
}
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting product data:", error);
|
console.error("Error deleting product data:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import { cookies } from 'next/headers';
|
|||||||
import { redirect } from 'next/navigation';
|
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,
|
endpoint: string,
|
||||||
options: RequestInit = {}
|
options: RequestInit = {}
|
||||||
): Promise<T> => {
|
): Promise<T> {
|
||||||
const cookieStore = cookies();
|
const cookieStore = cookies();
|
||||||
const authToken = await cookieStore.get('Authorization')?.value;
|
const authToken = cookieStore.get('Authorization')?.value;
|
||||||
|
|
||||||
if (!authToken) redirect('/login');
|
if (!authToken) redirect('/login');
|
||||||
|
|
||||||
@@ -32,4 +32,4 @@ export const fetchServer = async <T = unknown>(
|
|||||||
console.error(`Server request to ${endpoint} failed:`, error);
|
console.error(`Server request to ${endpoint} failed:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { fetchData } from '@/lib/data-service';
|
||||||
|
|
||||||
export const fetchShippingMethods = async (authToken: string) => {
|
export const fetchShippingMethods = async (authToken: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options`,
|
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -11,8 +13,10 @@ export const fetchShippingMethods = async (authToken: string) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Failed to fetch shipping options");
|
console.log("Shipping Methods Response:", res);
|
||||||
return await res.json();
|
|
||||||
|
if (!res) throw new Error("Failed to fetch shipping options");
|
||||||
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading shipping options:", error);
|
console.error("Error loading shipping options:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -30,7 +34,7 @@ export const addShippingMethod = async (
|
|||||||
newShipping: Omit<ShippingMethod, "_id">
|
newShipping: Omit<ShippingMethod, "_id">
|
||||||
): Promise<ShippingMethod[]> => {
|
): Promise<ShippingMethod[]> => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options`,
|
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -61,7 +65,7 @@ export const addShippingMethod = async (
|
|||||||
|
|
||||||
export const deleteShippingMethod = async (authToken: string, id: string) => {
|
export const deleteShippingMethod = async (authToken: string, id: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`,
|
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
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");
|
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 };
|
return { success: res.status === 204 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting shipping method:", error);
|
console.error("Error deleting shipping method:", error);
|
||||||
@@ -85,7 +88,7 @@ export const updateShippingMethod = async (
|
|||||||
updatedShipping: any
|
updatedShipping: any
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetchData(
|
||||||
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`,
|
`${process.env.NEXT_PUBLIC_API_URL}/shipping-options/${id}`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -98,8 +101,8 @@ export const updateShippingMethod = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!res.ok) throw new Error("Failed to update shipping method");
|
if (!res) throw new Error("Failed to update shipping method");
|
||||||
return await res.json();
|
return res
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating shipping method:", error);
|
console.error("Error updating shipping method:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { fetchData } from '@/lib/data-service';
|
||||||
|
|
||||||
export const apiRequest = async <T = any>(endpoint: string, method: string = "GET", body?: T | null) => {
|
export const apiRequest = async <T = any>(endpoint: string, method: string = "GET", body?: T | null) => {
|
||||||
try {
|
try {
|
||||||
if (typeof document === "undefined") {
|
if (typeof document === "undefined") {
|
||||||
@@ -10,7 +12,6 @@ export const apiRequest = async <T = any>(endpoint: string, method: string = "GE
|
|||||||
?.split("=")[1];
|
?.split("=")[1];
|
||||||
|
|
||||||
if (!authToken){
|
if (!authToken){
|
||||||
// go to /login
|
|
||||||
document.location.href = "/login";
|
document.location.href = "/login";
|
||||||
throw new Error("No authentication token found");
|
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;
|
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");
|
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 errorResponse = await res.json().catch(() => null);
|
||||||
const errorMessage = errorResponse?.error || res.statusText || "Unknown error";
|
const errorMessage = errorResponse?.error || res.statusText || "Unknown error";
|
||||||
throw new Error(`Failed to ${method} ${endpoint}: ${errorMessage}`);
|
throw new Error(`Failed to ${method} ${endpoint}: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Return JSON response
|
// ✅ Return JSON response
|
||||||
return await res.json();
|
return res;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`🚨 API Request Error: ${error.message}`);
|
console.error(`🚨 API Request Error: ${error.message}`);
|
||||||
|
|||||||
@@ -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")],
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user