diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
new file mode 100644
index 0000000..82fa097
--- /dev/null
+++ b/src/app/admin/page.tsx
@@ -0,0 +1,377 @@
+"use client";
+
+import { useState } from "react";
+import { products } from "../../data/products";
+import discountCodes from "../../data/discountCodes.json";
+import type { DiscountCode } from "../../types/discount";
+
+export default function AdminPage() {
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState("");
+
+ const handleLogin = async () => {
+ const response = await fetch("/api/admin/auth", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ password }),
+ });
+
+ if (response.ok) {
+ setIsAuthenticated(true);
+ setError("");
+ } else {
+ setError("Invalid password");
+ }
+ };
+
+ if (!isAuthenticated) {
+ return (
+
+
+
+ Admin Login
+
+
+
setPassword(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleLogin()}
+ />
+
+ {error &&
{error}
}
+
+
+
+ );
+ }
+
+ return ;
+}
+
+function AdminDashboard() {
+ const [activeTab, setActiveTab] = useState<"products" | "discounts">(
+ "products",
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {activeTab === "products" &&
}
+ {activeTab === "discounts" &&
}
+
+
+
+
+
+ );
+}
+
+function ProductsManager() {
+ const [productList, setProductList] = useState(products);
+ const [editingProduct, setEditingProduct] = useState(null);
+
+ const updatePrice = async (productId: string, newPrice: number) => {
+ const response = await fetch("/api/admin/products", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ productId, pricePerStack: newPrice }),
+ });
+
+ if (response.ok) {
+ setProductList((prev) =>
+ prev.map((product) =>
+ product.id === productId
+ ? { ...product, pricePerStack: newPrice }
+ : product,
+ ),
+ );
+ setEditingProduct(null);
+ }
+ };
+
+ return (
+
+ );
+}
+
+function DiscountManager() {
+ const [discountList, setDiscountList] =
+ useState>(discountCodes);
+ const [newCode, setNewCode] = useState("");
+ const [newPercentage, setNewPercentage] = useState(0);
+ const [newExpiration, setNewExpiration] = useState("");
+ const [newDescription, setNewDescription] = useState("");
+
+ const addDiscountCode = async () => {
+ if (!newCode || newPercentage <= 0) return;
+
+ const codeData: DiscountCode = {
+ percentage: newPercentage,
+ description: newDescription,
+ };
+
+ if (newExpiration) {
+ codeData.expiration = new Date(newExpiration).toISOString();
+ }
+
+ const response = await fetch("/api/admin/discounts", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ code: newCode, ...codeData }),
+ });
+
+ if (response.ok) {
+ setDiscountList((prev) => ({
+ ...prev,
+ [newCode]: codeData,
+ }));
+ setNewCode("");
+ setNewPercentage(0);
+ setNewExpiration("");
+ setNewDescription("");
+ }
+ };
+
+ const deleteDiscountCode = async (code: string) => {
+ const response = await fetch("/api/admin/discounts", {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ code }),
+ });
+
+ if (response.ok) {
+ setDiscountList((prev) => {
+ const updated = { ...prev };
+ delete updated[code];
+ return updated;
+ });
+ }
+ };
+
+ return (
+
+
Discount Codes
+
+
+
+
+
+
+
+ | Code |
+ Percentage |
+ Expiration |
+ Description |
+ Status |
+ Actions |
+
+
+
+ {Object.entries(discountList).map(([code, data]) => {
+ const isExpired =
+ data.expiration && new Date() > new Date(data.expiration);
+ return (
+
+ | {code} |
+
+ {data.percentage}%
+ |
+
+ {data.expiration
+ ? new Date(data.expiration).toLocaleDateString()
+ : "Never"}
+ |
+
+ {data.description || "-"}
+ |
+
+
+ {isExpired ? "Expired" : "Active"}
+
+ |
+
+
+ |
+
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/src/app/api/admin/auth/route.ts b/src/app/api/admin/auth/route.ts
new file mode 100644
index 0000000..1b82381
--- /dev/null
+++ b/src/app/api/admin/auth/route.ts
@@ -0,0 +1,11 @@
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(request: NextRequest) {
+ const { password } = await request.json();
+
+ if (password === process.env.ADMIN_PASSWORD) {
+ return NextResponse.json({ success: true });
+ }
+
+ return NextResponse.json({ error: "Invalid password" }, { status: 401 });
+}
diff --git a/src/app/api/admin/discounts/route.ts b/src/app/api/admin/discounts/route.ts
new file mode 100644
index 0000000..cf99bc6
--- /dev/null
+++ b/src/app/api/admin/discounts/route.ts
@@ -0,0 +1,68 @@
+import { NextRequest, NextResponse } from "next/server";
+import { promises as fs } from "fs";
+import path from "path";
+import type { DiscountCode } from "../../../../types/discount";
+
+const discountCodesPath = path.join(
+ process.cwd(),
+ "src/data/discountCodes.json",
+);
+
+export async function POST(request: NextRequest) {
+ try {
+ const { code, percentage, expiration, description } = await request.json();
+
+ const fileContent = await fs.readFile(discountCodesPath, "utf8");
+ const discountCodes = JSON.parse(fileContent);
+
+ const newDiscountCode: DiscountCode = {
+ percentage,
+ description,
+ };
+
+ if (expiration) {
+ newDiscountCode.expiration = expiration;
+ }
+
+ discountCodes[code] = newDiscountCode;
+
+ await fs.writeFile(
+ discountCodesPath,
+ JSON.stringify(discountCodes, null, 2),
+ "utf8",
+ );
+
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ console.error("Error adding discount code:", error);
+ return NextResponse.json(
+ { error: "Failed to add discount code" },
+ { status: 500 },
+ );
+ }
+}
+
+export async function DELETE(request: NextRequest) {
+ try {
+ const { code } = await request.json();
+
+ const fileContent = await fs.readFile(discountCodesPath, "utf8");
+ const discountCodes = JSON.parse(fileContent);
+
+ delete discountCodes[code];
+
+ await fs.writeFile(
+ discountCodesPath,
+ JSON.stringify(discountCodes, null, 2),
+ "utf8",
+ );
+
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ console.error("Error deleting discount code:", error);
+ return NextResponse.json(
+ { error: "Failed to delete discount code" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/admin/products/route.ts b/src/app/api/admin/products/route.ts
new file mode 100644
index 0000000..52cbc15
--- /dev/null
+++ b/src/app/api/admin/products/route.ts
@@ -0,0 +1,28 @@
+import { NextRequest, NextResponse } from "next/server";
+import { promises as fs } from "fs";
+import path from "path";
+
+const productsPath = path.join(process.cwd(), "src/data/products.ts");
+
+export async function PUT(request: NextRequest) {
+ try {
+ const { productId, pricePerStack } = await request.json();
+
+ const fileContent = await fs.readFile(productsPath, "utf8");
+
+ const updatedContent = fileContent.replace(
+ new RegExp(`(id:\\s*"${productId}"[\\s\\S]*?pricePerStack:\\s*)[\\d.]+`),
+ `$1${pricePerStack}`,
+ );
+
+ await fs.writeFile(productsPath, updatedContent, "utf8");
+
+ return NextResponse.json({ success: true });
+ } catch (error) {
+ console.error("Error updating product:", error);
+ return NextResponse.json(
+ { error: "Failed to update product" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/components/CartView.tsx b/src/components/CartView.tsx
index 6ad29af..ad6d876 100644
--- a/src/components/CartView.tsx
+++ b/src/components/CartView.tsx
@@ -4,6 +4,7 @@ import { useState } from "react";
import { useCart } from "../contexts/CartContext";
import { formatDiamonds } from "../utils/formatDiamonds";
import discountCodes from "../data/discountCodes.json";
+import type { DiscountCode } from "../types/discount";
export default function CartView() {
const { state, dispatch } = useCart();
@@ -32,9 +33,22 @@ export default function CartView() {
const applyDiscountCode = () => {
const code = discountCode.toLowerCase();
- const discountInfo = discountCodes[code as keyof typeof discountCodes];
+ const discountInfo = (discountCodes as Record)[code];
if (discountInfo) {
+ // Check if discount code has expired
+ if (discountInfo.expiration) {
+ const expirationDate = new Date(discountInfo.expiration);
+ const now = new Date();
+ if (now > expirationDate) {
+ dispatch({
+ type: "SET_DISCOUNT",
+ payload: { code: "", percentage: 0 },
+ });
+ return;
+ }
+ }
+
dispatch({
type: "SET_DISCOUNT",
payload: { code: discountCode, percentage: discountInfo.percentage },
diff --git a/src/components/ProductSelection.tsx b/src/components/ProductSelection.tsx
index c417695..0b96aeb 100644
--- a/src/components/ProductSelection.tsx
+++ b/src/components/ProductSelection.tsx
@@ -57,6 +57,7 @@ export default function ProductSelection() {
}}
className="w-full bg-gray-700 text-white p-3 rounded border border-gray-600 focus:border-green-400 focus:outline-none"
>
+
{products.map((product) => (