diff --git a/Archive.zip b/Archive.zip new file mode 100644 index 0000000..d69184c Binary files /dev/null and b/Archive.zip differ diff --git a/app/layout.tsx b/app/layout.tsx index df56de4..bd2e76d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,12 +1,13 @@ import { Inter } from "next/font/google" import "./globals.css" -import { ThemeProvider } from "@/components/layout/theme-provider"; +import { ThemeProvider } from "@/components/layout/theme-provider" +import type React from "react" // Import React const inter = Inter({ subsets: ["latin"] }) export const metadata = { title: "Ember", - description: "", + description: "E-commerce management dashboard", } export default function RootLayout({ @@ -23,5 +24,4 @@ export default function RootLayout({ ) -} - +} \ No newline at end of file diff --git a/components/dashboard/content.tsx b/components/dashboard/content.tsx index ae00c76..ce8db72 100644 --- a/components/dashboard/content.tsx +++ b/components/dashboard/content.tsx @@ -1,45 +1,26 @@ -"use client"; +"use client" -import { useState, useEffect } from "react"; -import { Package, Clock, CheckCircle, AlertTriangle } from "lucide-react"; -import OrderStats from "./order-stats"; - -interface OrderStatsData { - totalOrders: number; - pendingOrders: number; - completedOrders: number; - cancelledOrders: number; -} +import { useState, useEffect } from "react" +import OrderStats from "./order-stats" +import { getGreeting } from "@/lib/utils" +import { statsConfig } from "@/config/dashboard" +import type { OrderStatsData } from "@/lib/types" interface ContentProps { - username: string; - orderStats: OrderStatsData; + username: string + orderStats: OrderStatsData } -const getGreeting = () => { - const hour = new Date().getHours(); - if (hour < 12) return "Good morning"; - if (hour < 18) return "Good afternoon"; - return "Good evening"; -}; - export default function Content({ username, orderStats }: ContentProps) { - const [greeting, setGreeting] = useState(""); + const [greeting, setGreeting] = useState("") useEffect(() => { - setGreeting(getGreeting()); - }, []); - - const statsConfig = [ - { title: "Total Orders", value: orderStats.totalOrders, icon: Package }, - { title: "Completed Orders", value: orderStats.completedOrders, icon: CheckCircle }, - { title: "Pending Orders", value: orderStats.pendingOrders, icon: Clock }, - { title: "Cancelled Orders", value: orderStats.cancelledOrders, icon: AlertTriangle }, - ]; + setGreeting(getGreeting()) + }, []) return (
-

+

{greeting}, {username}!

@@ -48,11 +29,12 @@ export default function Content({ username, orderStats }: ContentProps) { ))}
- ); -} \ No newline at end of file + ) +} + diff --git a/components/dashboard/order-stats.tsx b/components/dashboard/order-stats.tsx index 21de69d..a9051d0 100644 --- a/components/dashboard/order-stats.tsx +++ b/components/dashboard/order-stats.tsx @@ -1,21 +1,23 @@ -import type { LucideIcon } from "lucide-react"; +import type { LucideIcon } from "lucide-react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" interface OrderStatsProps { - title: string; - value: string; - icon: LucideIcon; + title: string + value: string + icon: LucideIcon } export default function OrderStats({ title, value, icon: Icon }: OrderStatsProps) { return ( -
-
-
-

{title}

- -
-

{value}

-
-
- ); -} \ No newline at end of file + + + {title} + + + +
{value}
+
+
+ ) +} + diff --git a/components/layout/layout.tsx b/components/layout/layout.tsx index 22acb2c..67e6e00 100644 --- a/components/layout/layout.tsx +++ b/components/layout/layout.tsx @@ -1,29 +1,29 @@ -"use client"; +"use client" -import { useState, useEffect } from "react"; -import { useTheme } from "next-themes"; -import Sidebar from "./sidebar"; +import { useState, useEffect } from "react" +import { useTheme } from "next-themes" +import Sidebar from "./sidebar" +import type React from "react" // Added import for React interface LayoutProps { - children: React.ReactNode; + children: React.ReactNode } export default function Layout({ children }: LayoutProps) { - const { theme } = useTheme(); - const [mounted, setMounted] = useState(false); + const { theme } = useTheme() + const [mounted, setMounted] = useState(false) - useEffect(() => setMounted(true), []); + useEffect(() => setMounted(true), []) - if (!mounted) return null; + if (!mounted) return null return (
-
- {children} -
+
{children}
- ); -} \ No newline at end of file + ) +} + diff --git a/components/layout/nav-item.tsx b/components/layout/nav-item.tsx index 07b48b7..0286537 100644 --- a/components/layout/nav-item.tsx +++ b/components/layout/nav-item.tsx @@ -1,24 +1,22 @@ -import Link from "next/link"; +import Link from "next/link" +import type { LucideIcon } from "lucide-react" +import type React from "react" // Added import for React interface NavItemProps { - href: string; - icon: React.ElementType; - children: React.ReactNode; - onClick?: () => void; + href: string + icon: LucideIcon + children: React.ReactNode + onClick?: () => void } -export const NavItem: React.FC = ({ - href, - icon: Icon, - children, - onClick, -}) => ( +export const NavItem: React.FC = ({ href, icon: Icon, children, onClick }) => ( {children} -); +) + diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx index 5ac09fc..aa89dc4 100644 --- a/components/layout/sidebar.tsx +++ b/components/layout/sidebar.tsx @@ -1,114 +1,43 @@ -"use client"; - -import { fetchData } from "@/lib/data-service"; -import { useState } from "react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { - Package, - ShoppingCart, - Home, - Truck, - Box, - Settings, - LogOut, - Menu, -} from "lucide-react"; - -import { NavItem } from "./nav-item"; - -interface SidebarItem { - name: string; - href: string; - icon: React.ElementType; -} - -interface SidebarSection { - title: string; - items: SidebarItem[]; -} - -const sidebarConfig: SidebarSection[] = [ - { - title: "Overview", - items: [ - { name: "Dashboard", href: "/dashboard", icon: Home }, - { name: "Orders", href: "/dashboard/orders", icon: Package }, - ], - }, - { - title: "Management", - items: [ - { name: "Products", href: "/dashboard/products", icon: Box }, - { name: "Shipping", href: "/dashboard/shipping", icon: Truck }, - { name: "Storefront", href: "/dashboard/storefront", icon: Settings }, - ], - }, -]; +"use client" +import { useState } from "react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { ShoppingCart, LogOut } from "lucide-react" +import { NavItem } from "./nav-item" +import { Button } from "@/components/ui/button" +import { sidebarConfig } from "@/config/sidebar" const Sidebar: React.FC = () => { - const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); - const router = useRouter(); - - const handleLogout = async () => { - try { - const authToken = document.cookie.split("authToken=")[1]; - const res = await fetchData(`${process.env.NEXT_PUBLIC_API_URL}/logout`, { - method: "POST", - headers: { Authorization: `Bearer ${authToken}` }, - credentials: "include", - }); - - if (!res.ok) throw new Error("Logout failed"); - - document.cookie = - "authToken=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT;"; - router.push("/login"); - } catch (error) { - console.error("Logout error:", error); - } - }; + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false) + const router = useRouter() return ( <> - {/* Sidebar Navigation */} - {/* Mobile Menu Overlay */} {isMobileMenuOpen && (
{ /> )} - ); -}; + ) +} + +export default Sidebar -export default Sidebar; diff --git a/components/modals/product-modal.tsx b/components/modals/product-modal.tsx index 665752f..77de904 100644 --- a/components/modals/product-modal.tsx +++ b/components/modals/product-modal.tsx @@ -1,17 +1,18 @@ -"use client"; +"use client" -import { useState, useEffect } from "react"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { ImageUpload } from "@/components/forms/image-upload"; -import { PricingTiers } from "@/components/forms/pricing-tiers"; -import { ProductModalProps, ProductData } from "@/lib/types"; -import { toast } from "sonner"; +import { useState, useEffect } from "react" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { ImageUpload } from "@/components/forms/image-upload" +import { PricingTiers } from "@/components/forms/pricing-tiers" +import type { ProductModalProps, ProductData } from "@/lib/types" +import { toast } from "sonner" +import type React from "react" // Import React -export const ProductModal = ({ +export const ProductModal: React.FC = ({ open, onClose, onSave, @@ -19,92 +20,64 @@ export const ProductModal = ({ categories, editing, handleChange, + handleTieredPricingChange, + handleAddTier, + handleRemoveTier, setProductData, -}: ProductModalProps) => { - const [imagePreview, setImagePreview] = useState(null); - const [imageDimensions, setImageDimensions] = useState({ width: 300, height: 200 }); +}) => { + const [imagePreview, setImagePreview] = useState(null) + const [imageDimensions, setImageDimensions] = useState({ width: 300, height: 200 }) useEffect(() => { if (productData.image && typeof productData.image === "string") { - setImagePreview(productData.image); + setImagePreview(productData.image) } - }, [productData.image]); + }, [productData.image]) const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; + const file = e.target.files?.[0] if (!file) { - setProductData({ ...productData, image: null }); - setImagePreview(null); - return; + setProductData({ ...productData, image: null }) + setImagePreview(null) + return } - const image = new Image(); - const objectUrl = URL.createObjectURL(file); + const image = new Image() + const objectUrl = URL.createObjectURL(file) image.onload = () => { - const aspectRatio = image.naturalWidth / image.naturalHeight; - const width = aspectRatio > 1 ? 300 : 200 * aspectRatio; - const height = aspectRatio > 1 ? 300 / aspectRatio : 200; + const aspectRatio = image.naturalWidth / image.naturalHeight + const width = aspectRatio > 1 ? 300 : 200 * aspectRatio + const height = aspectRatio > 1 ? 300 / aspectRatio : 200 - setProductData({ ...productData, image: file }); - setImagePreview(objectUrl); - setImageDimensions({ width, height }); - }; + setProductData({ ...productData, image: file }) + setImagePreview(objectUrl) + setImageDimensions({ width, height }) + } - image.src = objectUrl; - }; - - const handleTierChange = (e: React.ChangeEvent, index: number) => { - const { name, valueAsNumber } = e.target; - - if (!["minQuantity", "pricePerUnit"].includes(name)) return; // ✅ Ensure valid keys - - const updatedPricing = [...productData.pricing]; - (updatedPricing[index] as any)[name] = isNaN(valueAsNumber) ? 0 : valueAsNumber; - - setProductData({ ...productData, pricing: updatedPricing }); - }; - - - const handleAddTier = () => { - setProductData(prev => ({ - ...prev, - pricing: [...prev.pricing, { minQuantity: 1, pricePerUnit: 0 }] - })); - }; - - const handleRemoveTier = (index: number) => { - const updatedPricing = productData.pricing.filter((_, i) => i !== index); - setProductData({ ...productData, pricing: updatedPricing }); - }; + image.src = objectUrl + } const handleSave = () => { - onSave(productData); - toast.success(editing ? "Product updated!" : "Product added!"); - onClose(); - }; + onSave(productData) + toast.success(editing ? "Product updated!" : "Product added!") + onClose() + } return ( - - {editing ? "Edit Product" : "Add Product"} - + {editing ? "Edit Product" : "Add Product"}
- {/* Left Column */} -
- -
- - {/* Right Column */} +
@@ -121,28 +94,23 @@ export const ProductModal = ({
- - +
- ); -}; + ) +} -const ProductBasicInfo = ({ - productData, - handleChange, - categories, - setProductData, -}: { - productData: ProductData; - handleChange: (e: React.ChangeEvent) => void; - categories: { _id: string; name: string }[]; - setProductData: React.Dispatch>; -}) => ( - <> +const ProductBasicInfo: React.FC<{ + productData: ProductData + handleChange: (e: React.ChangeEvent) => void + categories: { _id: string; name: string }[] + setProductData: React.Dispatch> +}> = ({ productData, handleChange, categories, setProductData }) => ( +
- + - - -); + +
+) -const CategorySelect = ({ - categories, - value, - setProductData, -}: { - categories: { _id: string; name: string }[]; - value: string; - setProductData: React.Dispatch>; -}) => ( +const CategorySelect: React.FC<{ + categories: { _id: string; name: string }[] + value: string + setProductData: React.Dispatch> +}> = ({ categories, value, setProductData }) => (
-); +) -const UnitTypeSelect = ({ - value, - setProductData, -}: { - value: string; - setProductData: React.Dispatch>; -}) => ( +const UnitTypeSelect: React.FC<{ + value: string + setProductData: React.Dispatch> +}> = ({ value, setProductData }) => (
-); \ No newline at end of file +) + diff --git a/components/ui/card.tsx b/components/ui/card.tsx index 764892e..0906be5 100644 --- a/components/ui/card.tsx +++ b/components/ui/card.tsx @@ -1,79 +1,44 @@ import * as React from "react" -import { cn } from "@/lib/styles"; +import { cn } from "@/lib/utils" -const Card = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
+const Card = React.forwardRef>(({ className, ...props }, ref) => ( +
)) Card.displayName = "Card" -const CardHeader = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +const CardHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +) CardHeader.displayName = "CardHeader" -const CardTitle = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +const CardTitle = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ), +) CardTitle.displayName = "CardTitle" -const CardDescription = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +const CardDescription = React.forwardRef>( + ({ className, ...props }, ref) => ( +

+ ), +) CardDescription.displayName = "CardDescription" -const CardContent = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -

-)) +const CardContent = React.forwardRef>( + ({ className, ...props }, ref) =>
, +) CardContent.displayName = "CardContent" -const CardFooter = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -
-)) +const CardFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ), +) CardFooter.displayName = "CardFooter" export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } + diff --git a/config/dashboard.ts b/config/dashboard.ts new file mode 100644 index 0000000..8018a94 --- /dev/null +++ b/config/dashboard.ts @@ -0,0 +1,9 @@ +import { Package, Clock, CheckCircle, AlertTriangle } from "lucide-react" + +export const statsConfig = [ + { title: "Total Orders", key: "totalOrders", icon: Package }, + { title: "Completed Orders", key: "completedOrders", icon: CheckCircle }, + { title: "Pending Orders", key: "pendingOrders", icon: Clock }, + { title: "Cancelled Orders", key: "cancelledOrders", icon: AlertTriangle }, +] + diff --git a/config/sidebar.ts b/config/sidebar.ts new file mode 100644 index 0000000..ce18c6e --- /dev/null +++ b/config/sidebar.ts @@ -0,0 +1,20 @@ +import { Home, Package, Box, Truck, Settings } from "lucide-react" + +export const sidebarConfig = [ + { + title: "Overview", + items: [ + { name: "Dashboard", href: "/dashboard", icon: Home }, + { name: "Orders", href: "/dashboard/orders", icon: Package }, + ], + }, + { + title: "Management", + items: [ + { name: "Products", href: "/dashboard/products", icon: Box }, + { name: "Shipping", href: "/dashboard/shipping", icon: Truck }, + { name: "Storefront", href: "/dashboard/storefront", icon: Settings }, + ], + }, +] + diff --git a/lib/types.ts b/lib/types.ts index 39864a3..4c6ee2b 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,69 +1,82 @@ -import { ChangeEvent, Dispatch, SetStateAction } from "react"; +import type { LucideIcon } from "lucide-react" +import type React from "react" export interface ProductModalProps { - open: boolean; - onClose: () => void; - onSave: (productData: ProductData) => void; - productData: ProductData; - categories: Category[]; - editing: boolean; - handleChange: (e: React.ChangeEvent) => void; - handleTieredPricingChange: (e: React.ChangeEvent, index: number) => void; - handleAddTier: () => void; // ✅ ADDED - handleRemoveTier: (index: number) => void; // ✅ ADDED - setProductData: React.Dispatch>; + open: boolean + onClose: () => void + onSave: (productData: ProductData) => void + productData: ProductData + categories: Category[] + editing: boolean + handleChange: (e: React.ChangeEvent) => void + handleTieredPricingChange: (e: React.ChangeEvent, index: number) => void + handleAddTier: () => void + handleRemoveTier: (index: number) => void + setProductData: React.Dispatch> } -// lib/types.ts export interface ShippingMethod { - _id?: string; // Optional before saving, required after fetching - name: string; - price: number; + _id?: string + name: string + price: number } export interface ShippingData { - _id?: string; // Optional before saving - name: string; - price: number; + _id?: string + name: string + price: number } export type ApiResponse = { - data?: T; - error?: string; - total?: number; -}; - - + data?: T + error?: string + total?: number +} export interface Product { - _id?: string; - name: string; - description: string; - stock?: number; - unitType: string; - category: string; - pricing: PricingTier[]; - image?: string | File | null; + _id?: string + name: string + description: string + stock?: number + unitType: string + category: string + pricing: PricingTier[] + image?: string | File | null } -export interface ProductData { - _id?: string; - name: string; - description: string; - stock?: number; - unitType: string; - category: string; - pricing: PricingTier[]; - image?: string | File | null; -} +export interface ProductData extends Product {} export interface PricingTier { - minQuantity: number; - pricePerUnit: number; - _id?: string; + minQuantity: number + pricePerUnit: number + _id?: string } export interface Category { - _id: string; - name: string; + _id: string + name: string +} + +export interface OrderStatsData { + totalOrders: number + pendingOrders: number + completedOrders: number + cancelledOrders: number +} + +export interface Order { + _id: string + orderId: string + status: OrderStatus + totalPrice: number + createdAt: string + telegramUsername?: string +} + +export type OrderStatus = "paid" | "unpaid" | "confirming" | "shipped" | "completed" | "disputed" | "cancelled" + +export interface StatusConfig { + icon: LucideIcon + color: string + animate?: string } \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..4152efa --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,13 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)) +} + +export const getGreeting = () => { + const hour = new Date().getHours() + if (hour < 12) return "Good morning" + if (hour < 18) return "Good afternoon" + return "Good evening" +} \ No newline at end of file