Files
group-2-threaded-project-pe…/web/app/contact/page.js
augmentedpotato 077d147498 Styling refactor
2026-04-18 16:22:38 -06:00

143 lines
6.6 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";
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";
}
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);
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));
}, []);
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>
);
}