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:
99
app/page.tsx
99
app/page.tsx
@@ -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>
|
||||
|
||||
@@ -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