Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,019 changes: 1,013 additions & 6 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@hookform/resolvers": "^5.4.0",
"@prisma/client": "^7.8.0",
"clsx": "^2.1.1",
"firebase": "^12.13.0",
"framer-motion": "^12.40.0",
"lucide-react": "^1.16.0",
"next": "15.5.18",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assests/apothecary/51FVHjwvJTL._SX522_.jpg
Binary file added public/assests/apothecary/51wtkdDB2IL._SX425_.jpg
Binary file added public/assests/apothecary/61ny25CJwFL._SX522_.jpg
46 changes: 36 additions & 10 deletions src/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"use client";

import React, { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuthStore } from "@/store/authStore";
import { useOrderStore } from "@/store/orderStore";
import { Navbar } from "@/components/Navbar";
import { Footer } from "@/components/Footer";
import { PetalBadge } from "@/components/PetalBadge";
Expand Down Expand Up @@ -33,12 +36,36 @@ import {
import { motion, AnimatePresence } from "framer-motion";

export default function AdminDashboardPage() {
const router = useRouter();
const { addToast } = useUIStore();
const { user } = useAuthStore();

// Protect Route: Admin role required
useEffect(() => {
if (!user) {
addToast("Administrator session required.", "error");
router.push("/login/admin");
} else if (user.role !== "admin") {
addToast("Access denied. Customer account is unauthorized.", "error");
router.push("/");
}
}, [user, router]);

const [activeTab, setActiveTab] = useState<"analytics" | "orders" | "products" | "coupons">("analytics");

// Sync with notification query parameters
useEffect(() => {
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
const tabParam = params.get("tab");
if (tabParam && ["analytics", "orders", "products", "coupons"].includes(tabParam)) {
setActiveTab(tabParam as any);
}
}
}, []);

// Dynamic lists from mock DB
const [orders, setOrders] = useState<MockOrder[]>([]);
const { orders, setOrders } = useOrderStore();
const [products, setProducts] = useState<MockProduct[]>([]);
const [coupons, setCoupons] = useState<MockCoupon[]>([]);

Expand Down Expand Up @@ -84,8 +111,7 @@ export default function AdminDashboardPage() {

const handleUpdateStatus = async (orderId: string, newStatus: string) => {
try {
const updated = await updateOrderStatus(orderId, newStatus);
setOrders((prev) => prev.map((o) => (o.id === orderId ? { ...o, status: updated.status } : o)));
await updateOrderStatus(orderId, newStatus);
addToast(`Order #${orderId} marked as ${newStatus}.`, "success");
} catch (err) {
addToast("Failed to update status.", "error");
Expand Down Expand Up @@ -168,7 +194,7 @@ export default function AdminDashboardPage() {
<div className="bg-white border border-petal-border p-5 rounded-card shadow-sm flex justify-between items-center">
<div>
<span className="text-[10px] uppercase font-bold tracking-widest text-stone-400">Total Revenue</span>
<h3 className="text-2xl font-bold text-petal-text-primary mt-1">${totalRevenue.toFixed(2)}</h3>
<h3 className="text-2xl font-bold text-petal-text-primary mt-1">Rs {totalRevenue.toFixed(2)}</h3>
</div>
<div className="w-10 h-10 bg-rose-50 text-petal-rose rounded-full flex items-center justify-center">
<TrendingUp size={16} />
Expand All @@ -190,7 +216,7 @@ export default function AdminDashboardPage() {
<div className="bg-white border border-petal-border p-5 rounded-card shadow-sm flex justify-between items-center">
<div>
<span className="text-[10px] uppercase font-bold tracking-widest text-stone-400">Average Order</span>
<h3 className="text-2xl font-bold text-petal-text-primary mt-1">${aov.toFixed(2)}</h3>
<h3 className="text-2xl font-bold text-petal-text-primary mt-1">Rs {aov.toFixed(2)}</h3>
</div>
<div className="w-10 h-10 bg-sky-50 text-petal-sky rounded-full flex items-center justify-center">
<Sparkles size={16} />
Expand Down Expand Up @@ -380,7 +406,7 @@ export default function AdminDashboardPage() {
Payment Sum
</h5>
<p className="text-petal-text-secondary">
<strong>${ord.total.toFixed(2)}</strong> via {ord.paymentMethod}
<strong>Rs {ord.total.toFixed(2)}</strong> via {ord.paymentMethod}
</p>
</div>
</div>
Expand Down Expand Up @@ -481,7 +507,7 @@ export default function AdminDashboardPage() {
<td className="py-3 px-4 uppercase text-[9px] font-bold text-petal-lavender">
{categoryLabel}
</td>
<td className="py-3 px-4 font-bold text-petal-text-primary">${p.price}</td>
<td className="py-3 px-4 font-bold text-petal-text-primary">Rs {p.price}</td>
<td className="py-3 px-4">
<span className={p.stock > 5 ? "text-petal-sage" : "text-petal-rose font-bold"}>
{p.stock} units
Expand Down Expand Up @@ -564,7 +590,7 @@ export default function AdminDashboardPage() {
className="w-full bg-stone-50 border border-petal-border rounded-input px-3 py-2.5 text-xs text-petal-text-primary focus:outline-none"
>
<option value="PERCENT">Percent (%)</option>
<option value="FIXED">Fixed ($)</option>
<option value="FIXED">Fixed (Rs)</option>
</select>
</div>
</div>
Expand Down Expand Up @@ -592,7 +618,7 @@ export default function AdminDashboardPage() {
{c.code}
</span>
<div className="text-[10px] text-stone-400 mt-2 font-bold uppercase tracking-wider">
{c.type === "PERCENT" ? `${c.value}% reduction` : `$${c.value} direct credit`}
{c.type === "PERCENT" ? `${c.value}% reduction` : `Rs ${c.value} direct credit`}
</div>
</div>
<PetalBadge variant="sage" label="Active" />
Expand Down Expand Up @@ -675,7 +701,7 @@ export default function AdminDashboardPage() {
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<div>
<label className="block text-[10px] font-bold uppercase tracking-widest text-petal-text-primary mb-1.5">
Price (USD)
Price (Rs)
</label>
<input
type="number"
Expand Down
31 changes: 25 additions & 6 deletions src/app/checkout/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Footer } from "@/components/Footer";
import { useCartStore } from "@/store/cartStore";
import { useUIStore } from "@/store/uiStore";
import { createOrder, getCoupons } from "@/lib/db";
import { useAuthStore } from "@/store/authStore";
import {
MapPin,
CreditCard,
Expand Down Expand Up @@ -36,6 +37,18 @@ export default function CheckoutPage() {
const { addToast } = useUIStore();
const { items, couponCode, getSubtotal, getDiscountAmount, getDeliveryCost, getTotal, clearCart } =
useCartStore();
const { user, addNotification } = useAuthStore();

// Protect Route: Customer role required
useEffect(() => {
if (!user) {
addToast("Please log in as a customer to checkout items.", "error");
router.push("/login");
} else if (user.role === "admin") {
addToast("Administrators cannot checkout store items.", "error");
router.push("/admin");
}
}, [user, router]);

const [currentStep, setCurrentStep] = useState(0); // 0 = Address, 1 = Payment, 2 = Review, 3 = Success
const [placedOrderId, setPlacedOrderId] = useState("");
Expand Down Expand Up @@ -126,14 +139,20 @@ export default function CheckoutPage() {
}));

const newOrder = await createOrder({
userId: "mock-user-1",
userId: user ? user.uid : "mock-user-1",
items: dbItems,
total: total,
address: `${activeAddress.name}, ${activeAddress.street}, ${activeAddress.city}, ${activeAddress.postalCode}, ${activeAddress.country}`,
couponCode: couponCode || undefined,
paymentMethod: paymentMethod.toUpperCase(),
});

// Dispatch admin notification
addNotification(
newOrder.id,
`New order ${newOrder.id} placed by ${user?.name || "Customer"} (${user?.email || "anonymous"}) for Rs ${total.toFixed(2)}.`
);

setPlacedOrderId(newOrder.id);
addToast("Order placed successfully!", "success");
clearCart();
Expand Down Expand Up @@ -624,7 +643,7 @@ export default function CheckoutPage() {
</div>
<div className="text-right">
<div className="text-xs font-bold text-petal-text-primary">
${(item.price * item.quantity).toFixed(2)}
Rs {(item.price * item.quantity).toFixed(2)}
</div>
<div className="text-[10px] text-stone-400 font-semibold mt-0.5">
Qty: {item.quantity}
Expand Down Expand Up @@ -762,23 +781,23 @@ export default function CheckoutPage() {
<div className="space-y-3.5 text-xs text-petal-text-secondary font-medium">
<div className="flex justify-between">
<span>Subtotal ({items.reduce((sum, item) => sum + item.quantity, 0)} items)</span>
<span className="text-petal-text-primary">${subtotal.toFixed(2)}</span>
<span className="text-petal-text-primary">Rs {subtotal.toFixed(2)}</span>
</div>
{discount > 0 && (
<div className="flex justify-between text-petal-rose font-semibold">
<span>Active Coupon Discount</span>
<span>−${discount.toFixed(2)}</span>
<span>−Rs {discount.toFixed(2)}</span>
</div>
)}
<div className="flex justify-between">
<span>Standard Shipping</span>
<span className="text-petal-text-primary">
{delivery === 0 ? "Free" : `$${delivery.toFixed(2)}`}
{delivery === 0 ? "Free" : `Rs ${delivery.toFixed(2)}`}
</span>
</div>
<div className="flex justify-between text-sm font-bold text-petal-text-primary pt-3.5 border-t border-stone-100">
<span className="font-playfair text-base italic">Total</span>
<span className="font-playfair text-lg font-bold">${total.toFixed(2)}</span>
<span className="font-playfair text-lg font-bold">Rs {total.toFixed(2)}</span>
</div>
</div>

Expand Down
Loading