Files
group-2-threaded-project-pe…/web/app/contact/page.js
2026-04-20 19:19:30 -06:00

147 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import { useAuth } from "@/context/AuthContext";
const labelCls = "flex flex-col gap-[0.35rem] text-[0.9rem] font-semibold text-[#444]";
const inputCls = "px-[0.85rem] py-[0.6rem] border border-[#ddd] rounded-lg text-base outline-none transition-all focus:border-[#e68672] focus:shadow-[0_0_0_3px_rgba(230,134,114,0.2)]";
const submitBtnCls = "mt-2 py-3 bg-[#e68672] text-white border-none rounded-lg text-base font-bold cursor-pointer transition-all hover:bg-[#d4705e] active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed";
//Returns the image path for a store, guessing from the store name if no image is set
function getStoreImage(store) {
if (store.imageUrl) return store.imageUrl;
const name = store.storeName?.toLowerCase() ?? "";
if (name.includes("downtown")) return "/stores/downtown.webp";
if (name.includes("north")) return "/stores/north.webp";
if (name.includes("west")) return "/stores/west.webp";
return "/images/pet-placeholder.png";
}
//Contact page with a message form on the left and store location cards on the right
export default function ContactPage() {
const { token } = useAuth();
const [locations, setLocations] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [subject, setSubject] = useState("");
const [body, setBody] = useState("");
const [sending, setSending] = useState(false);
const [sendError, setSendError] = useState(null);
const [sendSuccess, setSendSuccess] = useState(false);
//Loads all store locations when the page first opens
useEffect(() => {
const params = new URLSearchParams({ page: "0", size: "100", sort: "storeName,asc" });
fetch(`/api/v1/stores?${params}`)
.then((res) => {
if (!res.ok) throw new Error("Unable to load store, please try again later.");
return res.json();
})
.then((data) => setLocations(data.content ?? []))
.catch(() => setError("Unable to load store, please try again later."))
.finally(() => setLoading(false));
}, []);
//Submits the contact form to the backend
async function handleSend(e) {
e.preventDefault();
if (!token) {
setSendError("Please log in to send a message.");
return;
}
setSending(true);
setSendError(null);
try {
const res = await fetch("/api/v1/contact", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({ subject, body }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setSendSuccess(true);
setSubject("");
setBody("");
} catch {
setSendError("Failed to send message. Please try again.");
} finally {
setSending(false);
}
}
return (
<main className="min-h-screen">
<section className="text-center py-16 px-8 bg-gradient-to-b from-[#f9f9f9] to-white">
<h1 className="text-5xl font-bold text-[#333] mb-4 tracking-tight max-[768px]:text-3xl max-[480px]:text-[1.6rem]">Contact Us</h1>
<p className="text-2xl font-light text-[#666] mb-8 max-[768px]:text-[1.2rem]">Reach the team, find a location, or send us a message.</p>
<div className="w-[100px] h-1 bg-[#e68672] mx-auto mt-8 rounded-sm"></div>
</section>
<section className="max-w-[1200px] mx-auto px-8 pb-16 grid grid-cols-2 gap-8 max-[900px]:grid-cols-1">
<div className="bg-white rounded-2xl shadow-[0_4px_16px_rgba(0,0,0,0.08)] p-8">
<h2 className="text-[1.4rem] font-bold text-[#222] mb-4">Get in Touch</h2>
<p className="text-[#555] mb-2">Email: hello@leonspetstore.com.au</p>
<p className="text-[#555] mb-2">Phone: (03) 9000 0000</p>
<p className="text-[#555] mb-6">Hours: MonSat, 9:00 AM 6:00 PM</p>
<div className="mt-6">
<h3 className="text-[1.1rem] font-bold text-[#333] mb-4">Send Us a Message</h3>
{sendSuccess ? (
<p className="bg-[#f0fdf4] border border-[#bbf7d0] text-[#16a34a] rounded-lg px-4 py-3 text-[0.9rem]">Your message has been sent. We&apos;ll be in touch soon.</p>
) : (
<form className="flex flex-col gap-4" onSubmit={handleSend}>
<label className={labelCls}>
Subject
<input className={inputCls} type="text" value={subject} onChange={(e) => setSubject(e.target.value)} required maxLength={150} />
</label>
<label className={labelCls}>
Message
<textarea className={`${inputCls} resize-y`} value={body} onChange={(e) => setBody(e.target.value)} required maxLength={2000} rows={5} />
</label>
{sendError && <p className="bg-[#fff0f0] border border-[#f5c6c6] text-[#c0392b] rounded-lg px-4 py-[0.65rem] text-[0.9rem]">{sendError}</p>}
<button className={submitBtnCls} type="submit" disabled={sending}>
{sending ? "Sending…" : "Send Message"}
</button>
</form>
)}
</div>
</div>
<div className="bg-white rounded-2xl shadow-[0_4px_16px_rgba(0,0,0,0.08)] p-8">
<h2 className="text-[1.4rem] font-bold text-[#222] mb-4">Store Locations</h2>
{loading && <p className="text-[#666]">Loading locations...</p>}
{error && <p className="text-[#c0392b]">{error}</p>}
{!loading && !error && locations.length === 0 && <p className="text-[#666]">No store locations found.</p>}
{!loading && !error && locations.length > 0 && (
<div className="grid grid-cols-2 gap-4 max-[600px]:grid-cols-1">
{locations.map((location) => (
<article key={location.storeId} className="rounded-xl border border-[#eee] overflow-hidden">
<div className="aspect-video overflow-hidden bg-[#f5f5f5]">
<img
src={getStoreImage(location)}
alt={location.storeName}
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null;
e.currentTarget.src = "/images/pet-placeholder.png";
}}
/>
</div>
<div className="p-4">
<h3 className="font-bold text-[#222] mb-1">{location.storeName}</h3>
<p className="text-[0.85rem] text-[#666] mb-0.5">{location.address}</p>
<p className="text-[0.85rem] text-[#666] mb-0.5">{location.phone}</p>
<p className="text-[0.85rem] text-[#666]">{location.email}</p>
</div>
</article>
))}
</div>
)}
</div>
</section>
</main>
);
}