Refactor UI to remove Christmas theme and improve actions
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:
g
2026-01-12 07:43:33 +00:00
parent 244014f33a
commit e9737c8b24
4 changed files with 212 additions and 127 deletions

View File

@@ -1,77 +1,43 @@
import { getPlatformStatsServer } from "@/lib/server-api";
import { HomeNavbar } from "@/components/home-navbar";
import { Suspense } from "react";
import { Shield, LineChart, Zap, ArrowRight, CheckCircle2, Sparkles } from "lucide-react";
import { Shield, LineChart, Zap, ArrowRight, Sparkles } from "lucide-react";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { AnimatedStatsSection } from "@/components/animated-stats-section";
import { isDecember } from "@/lib/utils/christmas";
import { MotionWrapper } from "@/components/ui/motion-wrapper";
export const dynamic = 'force-dynamic';
const PY_20 = 20;
const PY_32 = 32;
const PX_6 = 6;
const PX_10 = 10;
function formatNumberValue(num: number): string {
return new Intl.NumberFormat().format(Math.round(num));
}
// Format currency
function formatCurrencyValue(amount: number): string {
return new Intl.NumberFormat('en-GB', {
style: 'currency',
currency: 'GBP',
maximumFractionDigits: 0
}).format(amount);
}
// This is a server component
export default async function Home() {
try {
const stats = await getPlatformStatsServer();
const isDec = isDecember();
return (
<div className={`flex flex-col min-h-screen bg-black text-white ${isDec ? 'christmas-theme' : ''}`}>
<div className={`absolute inset-0 bg-gradient-to-br pointer-events-none scale-100 ${isDec
? 'from-red-500/10 via-green-500/5 to-transparent'
: 'from-[#D53F8C]/10 via-[#D53F8C]/3 to-transparent'
}`} />
<div className="flex flex-col min-h-screen bg-black text-white">
<div className="absolute inset-0 bg-gradient-to-br from-indigo-500/10 via-purple-500/5 to-transparent pointer-events-none scale-100" />
<HomeNavbar />
{/* Hero Section */}
<section className="relative overflow-hidden">
<div className="relative flex flex-col items-center px-4 py-20 md:py-32 mx-auto max-w-7xl">
<div className="flex flex-col items-center text-center space-y-6 max-w-3xl">
<div className={`inline-flex items-center px-4 py-2 rounded-full border mb-4 ${isDec
? 'bg-red-500/10 border-red-500/20'
: 'bg-[#D53F8C]/10 border-[#D53F8C]/20'
}`}>
<Sparkles className={`h-4 w-4 mr-2 ${isDec ? 'text-red-400' : 'text-[#D53F8C]'}`} />
<span className={`text-sm font-medium ${isDec ? 'text-red-400' : 'text-[#D53F8C]'}`}>
<div className="inline-flex items-center px-4 py-2 rounded-full border border-indigo-500/20 bg-indigo-500/10 mb-4">
<Sparkles className="h-4 w-4 mr-2 text-indigo-400" />
<span className="text-sm font-medium text-indigo-400">
Secure Crypto Payments
</span>
</div>
<h1 className="text-4xl md:text-6xl font-bold tracking-tight">
The Future of <span className={isDec ? 'text-red-400' : 'text-[#D53F8C]'}>E-commerce</span> Management
The Future of <span className="text-indigo-500">E-commerce</span> Management
</h1>
<p className="text-lg md:text-xl text-zinc-400 max-w-2xl">
{isDec
? 'Spread joy this holiday season with our all-in-one platform. Secure payments, order tracking, and analytics wrapped up in one beautiful package. 🎄'
: 'Streamline your online business with our all-in-one platform. Secure payments, order tracking, and analytics in one place.'
}
Streamline your online business with our all-in-one platform. Secure payments, order tracking, and analytics in one place.
</p>
<div className="flex flex-col sm:flex-row gap-4 mt-4">
<Link href="/dashboard">
<Button
size="lg"
className={`gap-2 text-white border-0 h-12 px-8 ${isDec
? 'bg-gradient-to-r from-red-500 to-green-500 hover:from-red-600 hover:to-green-600'
: 'bg-[#D53F8C] hover:bg-[#B83280]'
}`}
className="gap-2 text-white border-0 h-12 px-8 bg-indigo-600 hover:bg-indigo-700"
>
Get Started
<ArrowRight className="h-4 w-4" />
@@ -103,35 +69,21 @@ export default async function Home() {
title: "Lightning Fast",
description: "Optimized for speed with real-time updates and instant notifications."
}
].map((feature, i) => {
const christmasColors = ['from-red-500/5', 'from-green-500/5', 'from-yellow-500/5'];
const christmasBorders = ['border-red-500/30', 'border-green-500/30', 'border-yellow-500/30'];
const christmasIcons = ['text-red-400', 'text-green-400', 'text-yellow-400'];
const christmasBgs = ['bg-red-500/10', 'bg-green-500/10', 'bg-yellow-500/10'];
return (
<div
key={i}
className={`group relative overflow-hidden rounded-xl bg-gradient-to-b p-6 border transition-all duration-300 hover:scale-[1.02] hover:shadow-lg ${isDec
? `from-zinc-800/30 to-transparent ${christmasBorders[i % 3]}`
: 'from-zinc-800/30 to-transparent border-zinc-800'
}`}
>
<div
className={`absolute inset-0 bg-gradient-to-b to-transparent opacity-0 group-hover:opacity-100 transition-opacity ${isDec ? christmasColors[i % 3] : 'from-[#D53F8C]/5'
}`}
/>
<div className="relative">
<div className={`h-12 w-12 flex items-center justify-center rounded-lg mb-4 ${isDec ? christmasBgs[i % 3] : 'bg-[#D53F8C]/10'
}`}>
<feature.icon className={`h-6 w-6 ${isDec ? christmasIcons[i % 3] : 'text-[#D53F8C]'}`} />
</div>
<h3 className="text-lg font-semibold text-white mb-2">{feature.title}</h3>
<p className="text-sm text-zinc-400">{feature.description}</p>
].map((feature, i) => (
<div
key={i}
className="group relative overflow-hidden rounded-xl bg-gradient-to-b from-zinc-800/30 to-transparent p-6 border border-zinc-800 transition-all duration-300 hover:scale-[1.02] hover:shadow-lg"
>
<div className="absolute inset-0 bg-gradient-to-b from-indigo-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
<div className="relative">
<div className="h-12 w-12 flex items-center justify-center rounded-lg mb-4 bg-indigo-500/10">
<feature.icon className="h-6 w-6 text-indigo-500" />
</div>
<h3 className="text-lg font-semibold text-white mb-2">{feature.title}</h3>
<p className="text-sm text-zinc-400">{feature.description}</p>
</div>
);
})}
</div>
))}
</div>
</MotionWrapper>
@@ -145,13 +97,6 @@ export default async function Home() {
{/* Footer */}
<footer className="relative py-12 px-4 mt-auto">
<div className="max-w-7xl mx-auto flex flex-col items-center">
{isDec && (
<div className="flex items-center gap-2 mb-4 text-red-400 animate-twinkle">
<span className="text-xl">🎄</span>
<span className="text-sm font-medium">Happy Holidays from da ember team!</span>
<span className="text-xl">🎄</span>
</div>
)}
<div className="text-sm text-zinc-500">
© {new Date().getFullYear()} Ember. All rights reserved.
</div>

View File

@@ -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>
);
}

View File

@@ -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}
/>
</>
)
}

View File

@@ -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>
);
}
}