Skip to content
Merged
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
43 changes: 42 additions & 1 deletion frontend/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ type OrderItem = {
email: string;
phone: string;
address: string;
paymentMethod: string;
gcashReference: string;
gcashReceiptUrl: string;
item: string;
size: string;
quantity: number;
Expand Down Expand Up @@ -74,6 +77,9 @@ export default function AdminPage() {
email: string;
phone: string;
address: string;
payment_method: string;
gcash_reference: string;
gcash_receipt_url: string;
item_name: string;
size: string;
quantity: number;
Expand All @@ -91,6 +97,9 @@ export default function AdminPage() {
email: order.email,
phone: order.phone,
address: order.address,
paymentMethod: order.payment_method,
gcashReference: order.gcash_reference,
gcashReceiptUrl: order.gcash_receipt_url,
item: order.item_name,
size: order.size,
quantity: order.quantity,
Expand Down Expand Up @@ -134,6 +143,9 @@ export default function AdminPage() {
order.phone,
order.item,
order.size,
order.paymentMethod,
order.gcashReference,
order.gcashReceiptUrl,
order.status,
].some((field) => field.toLowerCase().includes(normalized));
const matchesStatus =
Expand Down Expand Up @@ -229,6 +241,9 @@ export default function AdminPage() {
"Email",
"Phone",
"Address",
"Payment Method",
"GCash Reference",
"GCash Receipt",
"Item",
"Size",
"Qty",
Expand All @@ -244,6 +259,9 @@ export default function AdminPage() {
order.email,
order.phone,
order.address,
order.paymentMethod,
order.gcashReference,
order.gcashReceiptUrl,
order.item,
order.size,
String(order.quantity),
Expand Down Expand Up @@ -491,12 +509,13 @@ export default function AdminPage() {
</div>

<div className="mt-5 overflow-x-auto rounded-2xl border border-white/10">
<table className="w-full min-w-[1100px] text-left text-sm text-white/80">
<table className="w-full min-w-[1300px] text-left text-sm text-white/80">
<thead className="bg-white/5 text-xs uppercase tracking-[0.2em] text-white/60">
<tr>
<th className="px-4 py-3">Order</th>
<th className="px-4 py-3">Customer</th>
<th className="px-4 py-3">Address</th>
<th className="px-4 py-3">Payment</th>
<th className="px-4 py-3">Item</th>
<th className="px-4 py-3">Qty</th>
<th className="px-4 py-3">Price</th>
Expand Down Expand Up @@ -528,6 +547,28 @@ export default function AdminPage() {
{order.address}
</span>
</td>
<td className="px-4 py-3">
<div className="text-xs uppercase tracking-[0.2em] text-white/50">
{order.paymentMethod}
</div>
<div className="text-sm font-semibold text-white">
{order.gcashReference}
</div>
{order.gcashReceiptUrl ? (
<a
href={order.gcashReceiptUrl}
target="_blank"
rel="noreferrer"
className="text-xs uppercase tracking-[0.2em] text-emerald-200 hover:text-emerald-100"
>
View Receipt
</a>
) : (
<div className="text-xs text-white/40">
Missing receipt
</div>
)}
</td>
<td className="px-4 py-3">
<div className="font-semibold text-white">
{order.item}
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/api/admin/orders/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function GET(request: NextRequest) {
const { data, error } = await supabaseAdmin
.from("orders")
.select(
"id,full_name,email,phone,address,item_name,size,quantity,unit_price,line_total,status,created_at"
"id,full_name,email,phone,address,payment_method,gcash_reference,gcash_receipt_url,item_name,size,quantity,unit_price,line_total,status,created_at"
)
.order("created_at", { ascending: false });

Expand Down
102 changes: 99 additions & 3 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ type CartItem = {
quantity: number;
};

const GCASH_BUCKET = "gcash-receipts";
const MAX_RECEIPT_SIZE = 5 * 1024 * 1024;

export default function Home() {
const [merchItems, setMerchItems] = useState<MerchItem[]>([]);
const [merchLoading, setMerchLoading] = useState(true);
Expand Down Expand Up @@ -196,19 +199,71 @@ export default function Home() {
const email = String(formData.get("email") ?? "").trim();
const phone = String(formData.get("contactNumber") ?? "").trim();
const address = String(formData.get("address") ?? "").trim();
const paymentMethod = String(formData.get("paymentMethod") ?? "gcash");
const gcashReference = String(
formData.get("gcashReference") ?? ""
).trim();
const receiptFile = formData.get("gcashReceipt");

if (!fullName || !email || !phone || !address) {
setSubmitError("Please complete all contact fields.");
return;
}

if (paymentMethod !== "gcash") {
setSubmitError("Select GCash as your payment method.");
return;
}

if (!gcashReference) {
setSubmitError("Enter your GCash reference number.");
return;
}

if (!(receiptFile instanceof File) || receiptFile.size === 0) {
setSubmitError("Upload your GCash receipt screenshot.");
return;
}

if (!receiptFile.type.startsWith("image/")) {
setSubmitError("Receipt screenshot must be an image file.");
return;
}

if (receiptFile.size > MAX_RECEIPT_SIZE) {
setSubmitError("Receipt image must be 5MB or smaller.");
return;
}

setSubmitting(true);

const fileExtension =
receiptFile.name.split(".").pop()?.toLowerCase() || "jpg";
const uploadPath = `gcash/${crypto.randomUUID()}.${fileExtension}`;
const { error: uploadError } = await supabase.storage
.from(GCASH_BUCKET)
.upload(uploadPath, receiptFile, {
contentType: receiptFile.type,
});

if (uploadError) {
setSubmitError("Unable to upload receipt. Please try again.");
setSubmitting(false);
return;
}

const receiptUrl = supabase.storage
.from(GCASH_BUCKET)
.getPublicUrl(uploadPath).data.publicUrl;

const orderRows = cartItems.map((item) => ({
full_name: fullName,
email,
phone,
address,
payment_method: "gcash",
gcash_reference: gcashReference,
gcash_receipt_url: receiptUrl,
item_id: item.itemId,
item_name: item.name,
size: item.size,
Expand All @@ -229,7 +284,7 @@ export default function Home() {
setCartItems([]);
setQuantities((prev) => prev.map(() => 0));
form.reset();
setSubmitSuccess("Order received! We'll email you with next steps.");
setSubmitSuccess("Order received! We'll verify your GCash receipt.");
setSubmitting(false);
};
return (
Expand Down Expand Up @@ -538,8 +593,8 @@ export default function Home() {
Place Your Order
</h2>
<p className="mt-3 text-sm leading-6 text-white/70">
Fill up the form below to reserve your merch. We will follow up
with payment details and size confirmation.
Fill up the form below and attach your GCash receipt to confirm
your merch order.
</p>
<div className="mt-6 grid gap-4 text-xs uppercase tracking-[0.25em] text-white/60">
<div className="flex items-center gap-3">
Expand Down Expand Up @@ -594,6 +649,47 @@ export default function Home() {
/>
</label>

<div className="rounded-2xl border border-white/10 bg-white/5 p-4">
<p className="text-xs uppercase tracking-[0.2em] text-white/60">
Payment Method
</p>
<label className="mt-3 flex items-center gap-3 text-sm text-white/80">
<input
type="radio"
name="paymentMethod"
value="gcash"
defaultChecked
className="h-4 w-4 border-white/30 bg-white/10 text-emerald-300 focus:ring-emerald-300/40"
/>
GCash
</label>
<p className="mt-2 text-xs text-white/60">
Upload your GCash receipt before confirming the order.
</p>
</div>

<label className="text-sm text-white/80">
GCash Reference No.
<input
type="text"
name="gcashReference"
placeholder="0000000000"
required
className="mt-2 w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder-white/40 focus:border-emerald-300/70 focus:outline-none focus:ring-2 focus:ring-emerald-300/30"
/>
</label>

<label className="text-sm text-white/80">
GCash Receipt Screenshot
<input
type="file"
name="gcashReceipt"
accept="image/*"
required
className="mt-2 w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white file:mr-3 file:rounded-full file:border-0 file:bg-white/10 file:px-3 file:py-2 file:text-xs file:uppercase file:tracking-[0.2em] file:text-white/80"
/>
</label>

{submitError ? (
<p className="text-xs uppercase tracking-[0.2em] text-amber-300">
{submitError}
Expand Down
3 changes: 3 additions & 0 deletions frontend/supabase/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ create table if not exists public.orders (
email text not null,
phone text not null,
address text not null,
payment_method text not null default 'gcash',
gcash_reference text not null,
gcash_receipt_url text not null,
item_id uuid references public.merch_items(id),
item_name text not null,
size text not null,
Expand Down