Refactor UI to remove Christmas theme and improve actions
All checks were successful
Build Frontend / build (push) Successful in 1m11s
All checks were successful
Build Frontend / build (push) Successful in 1m11s
Removed all Christmas-specific theming and logic from the home page and navbar, standardizing colors to indigo. Updated QuickActions to open a modal for adding products instead of navigating to a new page, including logic for product creation and category fetching. Simplified ChatTable row animations and fixed minor layout issues. Updated button styles and mobile menu links for consistency.
This commit is contained in:
@@ -304,7 +304,7 @@ export default function ChatTable() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<AnimatePresence mode="popLayout">
|
||||
<AnimatePresence>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="h-32 text-center">
|
||||
@@ -328,10 +328,10 @@ export default function ChatTable() {
|
||||
chats.map((chat, index) => (
|
||||
<motion.tr
|
||||
key={chat._id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2, delay: index * 0.05 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="group cursor-pointer hover:bg-muted/30 transition-colors border-b border-border/50 last:border-0"
|
||||
onClick={() => handleChatClick(chat._id)}
|
||||
style={{ display: 'table-row' }} // Essential for table layout
|
||||
@@ -469,8 +469,8 @@ export default function ChatTable() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect, ChangeEvent } from "react"
|
||||
import Link from "next/link"
|
||||
import { motion } from "framer-motion"
|
||||
import {
|
||||
PlusCircle,
|
||||
Package,
|
||||
BarChart3,
|
||||
Settings,
|
||||
MessageSquare,
|
||||
Truck,
|
||||
Tag,
|
||||
Users
|
||||
BarChart3,
|
||||
MessageSquare,
|
||||
} from "lucide-react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import dynamic from "next/dynamic"
|
||||
import { Product } from "@/models/products"
|
||||
import { Category } from "@/models/categories"
|
||||
import { clientFetch } from "@/lib/api"
|
||||
import { toast } from "sonner"
|
||||
|
||||
const ProductModal = dynamic(() => import("@/components/modals/product-modal").then(mod => ({ default: mod.ProductModal })), {
|
||||
loading: () => null
|
||||
});
|
||||
|
||||
const actions = [
|
||||
{
|
||||
title: "Add Product",
|
||||
icon: PlusCircle,
|
||||
href: "/dashboard/products/new",
|
||||
href: "/dashboard/products/new", // Fallback text
|
||||
color: "bg-blue-500/10 text-blue-500",
|
||||
description: "Create a new listing"
|
||||
description: "Create a new listing",
|
||||
action: "modal"
|
||||
},
|
||||
{
|
||||
title: "Process Orders",
|
||||
@@ -46,19 +53,118 @@ const actions = [
|
||||
]
|
||||
|
||||
export default function QuickActions() {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [productData, setProductData] = useState<Product>({
|
||||
name: "",
|
||||
description: "",
|
||||
unitType: "pcs",
|
||||
category: "",
|
||||
pricing: [{ minQuantity: 1, pricePerUnit: 0 }],
|
||||
image: null,
|
||||
costPerUnit: 0,
|
||||
});
|
||||
|
||||
// Fetch categories on mount
|
||||
useEffect(() => {
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const data = await clientFetch('/categories');
|
||||
setCategories(data);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch categories:", error);
|
||||
}
|
||||
};
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setProductData({ ...productData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleTieredPricingChange = (e: ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
const updatedPricing = [...productData.pricing];
|
||||
const name = e.target.name as "minQuantity" | "pricePerUnit";
|
||||
updatedPricing[index][name] = e.target.valueAsNumber || 0;
|
||||
setProductData({ ...productData, pricing: updatedPricing });
|
||||
};
|
||||
|
||||
const handleAddTier = () => {
|
||||
setProductData((prev) => ({
|
||||
...prev,
|
||||
pricing: [...prev.pricing, { minQuantity: 1, pricePerUnit: 0 }],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoveTier = (index: number) => {
|
||||
setProductData((prev) => ({
|
||||
...prev,
|
||||
pricing: prev.pricing.filter((_, i) => i !== index),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSaveProduct = async (data: Product, file?: File | null) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Prepare the product data
|
||||
const payload = {
|
||||
...data,
|
||||
stockTracking: data.stockTracking ?? true,
|
||||
currentStock: data.currentStock ?? 0,
|
||||
lowStockThreshold: data.lowStockThreshold ?? 10,
|
||||
stockStatus: data.stockStatus ?? 'out_of_stock'
|
||||
};
|
||||
|
||||
const productResponse = await clientFetch("/products", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/products/${productResponse._id}/image`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${document.cookie.split("; ").find((row) => row.startsWith("Authorization="))?.split("=")[1]}`,
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
|
||||
setModalOpen(false);
|
||||
setProductData({
|
||||
name: "",
|
||||
description: "",
|
||||
unitType: "pcs",
|
||||
category: "",
|
||||
pricing: [{ minQuantity: 1, pricePerUnit: 0 }],
|
||||
image: null,
|
||||
costPerUnit: 0,
|
||||
});
|
||||
toast.success("Product added successfully");
|
||||
|
||||
// Optional: trigger a refresh of products or stats if needed
|
||||
// currently just closing modal
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Failed to save product");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{actions.map((action, index) => (
|
||||
<motion.div
|
||||
key={action.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
<Link href={action.href}>
|
||||
<Card className="hover:border-primary/50 transition-colors cursor-pointer group h-full">
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{actions.map((action, index) => {
|
||||
const isModalAction = action.action === "modal";
|
||||
|
||||
const CardContentWrapper = () => (
|
||||
<Card className="hover:border-primary/50 transition-colors cursor-pointer group h-full border-border/40 bg-background/50 backdrop-blur-sm">
|
||||
<CardContent className="p-6 flex flex-col items-center text-center">
|
||||
<div className={`p-3 rounded-xl ${action.color} mb-4 group-hover:scale-110 transition-transform`}>
|
||||
<action.icon className="h-6 w-6" />
|
||||
@@ -67,9 +173,44 @@ export default function QuickActions() {
|
||||
<p className="text-sm text-muted-foreground mt-1">{action.description}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={action.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
>
|
||||
{isModalAction ? (
|
||||
<div onClick={() => setModalOpen(true)}>
|
||||
<CardContentWrapper />
|
||||
</div>
|
||||
) : (
|
||||
<Link href={action.href}>
|
||||
<CardContentWrapper />
|
||||
</Link>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<ProductModal
|
||||
open={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onSave={handleSaveProduct}
|
||||
productData={productData}
|
||||
categories={categories}
|
||||
editing={false}
|
||||
handleChange={handleChange}
|
||||
handleTieredPricingChange={handleTieredPricingChange}
|
||||
handleAddTier={handleAddTier}
|
||||
handleRemoveTier={handleRemoveTier}
|
||||
setProductData={setProductData}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LogIn } from "lucide-react";
|
||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||
import { useState } from "react";
|
||||
|
||||
export function HomeNavbar() {
|
||||
@@ -27,16 +26,16 @@ export function HomeNavbar() {
|
||||
Log In
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/auth/login">
|
||||
<Button className="bg-[#D53F8C] hover:bg-[#B83280] text-white border-0">Get Started</Button>
|
||||
<Link href="/dashboard">
|
||||
<Button className="bg-indigo-600 hover:bg-indigo-700 text-white border-0">Get Started</Button>
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="md:hidden">
|
||||
<Button variant="ghost" size="icon" onClick={() => setMenuOpen(!menuOpen)} className="text-white hover:bg-gray-900">
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
<svg
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
@@ -51,42 +50,42 @@ export function HomeNavbar() {
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Mobile menu */}
|
||||
{menuOpen && (
|
||||
<div className="md:hidden absolute top-16 left-0 right-0 bg-[#1C1C1C] border-b border-gray-800 p-4 z-50">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Link
|
||||
href="#features"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
<Link
|
||||
href="#features"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
Features
|
||||
</Link>
|
||||
<Link
|
||||
href="#benefits"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
<Link
|
||||
href="#benefits"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
Benefits
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
Log In
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="text-sm p-2 hover:bg-gray-900 rounded-md text-gray-300"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
Create Account
|
||||
Get Started
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user