Skip to content

Commit 7f129f9

Browse files
committed
[ADD] Adds Controller, Router, and Admin Pages for Merchants
1 parent 8041c0a commit 7f129f9

7 files changed

Lines changed: 726 additions & 1 deletion

File tree

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
"use client";
2+
import React, { useEffect, useState, use } from "react";
3+
import DashboardSidebar from "@/components/DashboardSidebar";
4+
import Link from "next/link";
5+
import { useRouter } from "next/navigation";
6+
import apiClient from "@/lib/api";
7+
import { toast } from "react-hot-toast";
8+
9+
interface Product {
10+
id: string;
11+
title: string;
12+
price: number;
13+
inStock: number;
14+
}
15+
16+
interface Merchant {
17+
id: string;
18+
name: string;
19+
email: string | null;
20+
phone: string | null;
21+
address: string | null;
22+
description: string | null;
23+
status: string;
24+
products: Product[];
25+
}
26+
27+
interface MerchantDetailPageProps {
28+
params: Promise<{ id: string }>;
29+
}
30+
31+
export default function MerchantDetailPage({
32+
params,
33+
}: MerchantDetailPageProps) {
34+
// Unwrap params using React.use()
35+
const resolvedParams = use(params);
36+
const id = resolvedParams.id;
37+
38+
const [merchant, setMerchant] = useState<Merchant | null>(null);
39+
const [loading, setLoading] = useState(true);
40+
const [formData, setFormData] = useState({
41+
name: "",
42+
email: "",
43+
phone: "",
44+
address: "",
45+
description: "",
46+
status: "ACTIVE",
47+
});
48+
49+
const router = useRouter();
50+
51+
const fetchMerchant = async () => {
52+
try {
53+
setLoading(true);
54+
const response = await apiClient.get(`/api/merchants/${id}`);
55+
56+
if (!response.ok) {
57+
if (response.status === 404) {
58+
router.push("/admin/merchant");
59+
return;
60+
}
61+
throw new Error("Failed to fetch merchant");
62+
}
63+
64+
const data = await response.json();
65+
setMerchant(data);
66+
setFormData({
67+
name: data.name || "",
68+
email: data.email || "",
69+
phone: data.phone || "",
70+
address: data.address || "",
71+
description: data.description || "",
72+
status: data.status || "ACTIVE",
73+
});
74+
} catch (error) {
75+
console.error("Error fetching merchant:", error);
76+
toast.error("Failed to load merchant details");
77+
} finally {
78+
setLoading(false);
79+
}
80+
};
81+
82+
useEffect(() => {
83+
fetchMerchant();
84+
}, [id]);
85+
86+
const handleInputChange = (
87+
e: React.ChangeEvent<
88+
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
89+
>
90+
) => {
91+
const { name, value } = e.target;
92+
setFormData((prev) => ({ ...prev, [name]: value }));
93+
};
94+
95+
const handleSubmit = async (e: React.FormEvent) => {
96+
e.preventDefault();
97+
try {
98+
const response = await apiClient.put(`/api/merchants/${id}`, {
99+
method: "PUT",
100+
headers: { "Content-Type": "application/json" },
101+
body: JSON.stringify(formData),
102+
});
103+
104+
if (!response.ok) {
105+
throw new Error("Failed to update merchant");
106+
}
107+
108+
toast.success("Merchant updated successfully");
109+
fetchMerchant(); // Refresh data
110+
} catch (error) {
111+
console.error("Error updating merchant:", error);
112+
toast.error("Failed to update merchant");
113+
}
114+
};
115+
116+
const handleDelete = async () => {
117+
if (!confirm("Are you sure you want to delete this merchant?")) {
118+
return;
119+
}
120+
121+
try {
122+
const response = await apiClient.delete(`/api/merchants/${id}`);
123+
124+
if (!response.ok) {
125+
const data = await response.json();
126+
throw new Error(data.error || "Failed to delete merchant");
127+
}
128+
129+
toast.success("Merchant deleted successfully");
130+
router.push("/admin/merchant");
131+
} catch (error) {
132+
console.error("Error deleting merchant:", error);
133+
toast.error(
134+
typeof error === "object" && error !== null && "message" in error
135+
? (error as { message?: string }).message || "Failed to delete merchant"
136+
: "Failed to delete merchant"
137+
);
138+
}
139+
};
140+
141+
if (loading) {
142+
return (
143+
<div className="flex h-screen">
144+
<DashboardSidebar />
145+
<div className="flex-1 p-10 flex items-center justify-center">
146+
Loading merchant details...
147+
</div>
148+
</div>
149+
);
150+
}
151+
152+
if (!merchant) {
153+
return (
154+
<div className="flex h-screen">
155+
<DashboardSidebar />
156+
<div className="flex-1 p-10 flex items-center justify-center">
157+
Merchant not found
158+
</div>
159+
</div>
160+
);
161+
}
162+
163+
return (
164+
<div className="flex h-screen">
165+
<DashboardSidebar />
166+
<div className="flex-1 p-10 overflow-y-auto">
167+
<div className="flex justify-between items-center mb-6">
168+
<h1 className="text-3xl font-bold">Merchant Details</h1>
169+
<div className="flex gap-4">
170+
<Link
171+
href="/admin/merchant"
172+
className="bg-gray-500 text-white px-6 py-2 rounded-md hover:bg-gray-600 transition"
173+
>
174+
Back to Merchants
175+
</Link>
176+
<button
177+
onClick={handleDelete}
178+
className="bg-red-500 text-white px-6 py-2 rounded-md hover:bg-red-600 transition"
179+
>
180+
Delete Merchant
181+
</button>
182+
</div>
183+
</div>
184+
185+
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
186+
<form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-2 gap-6">
187+
<div>
188+
<label className="block text-gray-700 font-medium mb-2">Name</label>
189+
<input
190+
type="text"
191+
name="name"
192+
value={formData.name}
193+
onChange={handleInputChange}
194+
className="w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300"
195+
required
196+
/>
197+
</div>
198+
<div>
199+
<label className="block text-gray-700 font-medium mb-2">Email</label>
200+
<input
201+
type="email"
202+
name="email"
203+
value={formData.email}
204+
onChange={handleInputChange}
205+
className="w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300"
206+
/>
207+
</div>
208+
<div>
209+
<label className="block text-gray-700 font-medium mb-2">Phone</label>
210+
<input
211+
type="text"
212+
name="phone"
213+
value={formData.phone}
214+
onChange={handleInputChange}
215+
className="w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300"
216+
/>
217+
</div>
218+
<div>
219+
<label className="block text-gray-700 font-medium mb-2">Status</label>
220+
<select
221+
name="status"
222+
value={formData.status}
223+
onChange={handleInputChange}
224+
className="w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300"
225+
>
226+
<option value="ACTIVE">Active</option>
227+
<option value="INACTIVE">Inactive</option>
228+
</select>
229+
</div>
230+
<div className="md:col-span-2">
231+
<label className="block text-gray-700 font-medium mb-2">Address</label>
232+
<input
233+
type="text"
234+
name="address"
235+
value={formData.address}
236+
onChange={handleInputChange}
237+
className="w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300"
238+
/>
239+
</div>
240+
<div className="md:col-span-2">
241+
<label className="block text-gray-700 font-medium mb-2">Description</label>
242+
<textarea
243+
name="description"
244+
value={formData.description}
245+
onChange={handleInputChange}
246+
className="w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300 h-32"
247+
></textarea>
248+
</div>
249+
<div className="md:col-span-2">
250+
<button
251+
type="submit"
252+
className="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 transition"
253+
>
254+
Save Changes
255+
</button>
256+
</div>
257+
</form>
258+
</div>
259+
260+
<div className="bg-white rounded-lg shadow-md p-6">
261+
<h2 className="text-xl font-bold mb-4">Merchant Products</h2>
262+
{merchant.products.length > 0 ? (
263+
<table className="w-full">
264+
<thead>
265+
<tr className="border-b">
266+
<th className="py-3 text-left">Title</th>
267+
<th className="py-3 text-left">Price</th>
268+
<th className="py-3 text-left">In Stock</th>
269+
<th className="py-3 text-left">Actions</th>
270+
</tr>
271+
</thead>
272+
<tbody>
273+
{merchant.products.map((product) => (
274+
<tr key={product.id} className="border-b hover:bg-gray-50">
275+
<td className="py-4">{product.title}</td>
276+
<td className="py-4">${product.price / 100}</td>
277+
<td className="py-4">{product.inStock}</td>
278+
<td className="py-4">
279+
<Link
280+
href={`/admin/products/${product.id}`}
281+
className="text-blue-500 hover:underline"
282+
>
283+
View
284+
</Link>
285+
</td>
286+
</tr>
287+
))}
288+
</tbody>
289+
</table>
290+
) : (
291+
<p className="text-gray-500">No products for this merchant yet.</p>
292+
)}
293+
</div>
294+
</div>
295+
</div>
296+
);
297+
}

0 commit comments

Comments
 (0)