fix contact form and appt ui
This commit is contained in:
@@ -383,6 +383,10 @@ function AppointmentsPage() {
|
|||||||
|
|
||||||
const [showAddPetModal, setShowAddPetModal] = useState(false);
|
const [showAddPetModal, setShowAddPetModal] = useState(false);
|
||||||
const [cancellingId, setCancellingId] = useState(null);
|
const [cancellingId, setCancellingId] = useState(null);
|
||||||
|
const [apptSearch, setApptSearch] = useState("");
|
||||||
|
const [adoptionSearch, setAdoptionSearch] = useState("");
|
||||||
|
const [showPastAppts, setShowPastAppts] = useState(false);
|
||||||
|
const [showPastAdoptions, setShowPastAdoptions] = useState(false);
|
||||||
|
|
||||||
const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||||
|
|
||||||
@@ -966,11 +970,27 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
<h2 className="appt-form-title">{canBookAppointments ? "Your Appointments" : "Appointments"}</h2>
|
<h2 className="appt-form-title">{canBookAppointments ? "Your Appointments" : "Appointments"}</h2>
|
||||||
{loadingAppointments ? (
|
{loadingAppointments ? (
|
||||||
<p className="appt-loading">Loading appointments...</p>
|
<p className="appt-loading">Loading appointments...</p>
|
||||||
) : appointments.length === 0 ? (
|
) : (() => {
|
||||||
<p className="appt-empty">No appointments yet.</p>
|
const activeAppts = appointments.filter((a) => a.appointmentStatus?.toLowerCase() === "booked");
|
||||||
|
const pastAppts = appointments.filter((a) => a.appointmentStatus?.toLowerCase() !== "booked");
|
||||||
|
const q = apptSearch.toLowerCase();
|
||||||
|
const filteredActive = activeAppts.filter((a) =>
|
||||||
|
!q || [a.serviceName, a.storeName, a.petName].some((v) => v?.toLowerCase().includes(q))
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className="appt-search"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search appointments…"
|
||||||
|
value={apptSearch}
|
||||||
|
onChange={(e) => setApptSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
{filteredActive.length === 0 ? (
|
||||||
|
<p className="appt-empty">{activeAppts.length === 0 ? "No active appointments." : "No results."}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="appt-list">
|
<div className="appt-list">
|
||||||
{appointments.map((a) => (
|
{filteredActive.map((a) => (
|
||||||
<div key={a.appointmentId} className="appt-card">
|
<div key={a.appointmentId} className="appt-card">
|
||||||
<div className="appt-card-header">
|
<div className="appt-card-header">
|
||||||
<span className="appt-card-service">{a.serviceName}</span>
|
<span className="appt-card-service">{a.serviceName}</span>
|
||||||
@@ -983,11 +1003,8 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||||
</div>
|
</div>
|
||||||
{a.petName && (
|
{a.petName && (
|
||||||
<div className="appt-card-pets">
|
<div className="appt-card-pets">Pet: {a.petName}</div>
|
||||||
Pet: {a.petName}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{a.appointmentStatus?.toLowerCase() === "booked" && (
|
|
||||||
<div className="appt-card-actions">
|
<div className="appt-card-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -998,20 +1015,66 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
{cancellingId === a.appointmentId ? "Cancelling..." : "Cancel"}
|
{cancellingId === a.appointmentId ? "Cancelling..." : "Cancel"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{pastAppts.length > 0 && (
|
||||||
|
<div className="appt-past-section">
|
||||||
|
<button className="appt-past-toggle" onClick={() => setShowPastAppts((v) => !v)}>
|
||||||
|
{showPastAppts ? "Hide" : "Show"} past appointments ({pastAppts.length})
|
||||||
|
</button>
|
||||||
|
{showPastAppts && (
|
||||||
|
<div className="appt-list appt-list--past">
|
||||||
|
{pastAppts.map((a) => (
|
||||||
|
<div key={a.appointmentId} className="appt-card appt-card--past">
|
||||||
|
<div className="appt-card-header">
|
||||||
|
<span className="appt-card-service">{a.serviceName}</span>
|
||||||
|
<span className={`appt-card-status appt-card-status--${a.appointmentStatus?.toLowerCase()}`}>
|
||||||
|
{a.appointmentStatus}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="appt-card-details">
|
||||||
|
<span>{a.storeName}</span>
|
||||||
|
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||||
|
</div>
|
||||||
|
{a.petName && (
|
||||||
|
<div className="appt-card-pets">Pet: {a.petName}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
<h2 className="appt-form-title" style={{ marginTop: "2rem" }}>{canBookAppointments ? "Your Adoptions" : "Adoptions"}</h2>
|
<h2 className="appt-form-title" style={{ marginTop: "2rem" }}>{canBookAppointments ? "Your Adoptions" : "Adoptions"}</h2>
|
||||||
{loadingAdoptions ? (
|
{loadingAdoptions ? (
|
||||||
<p className="appt-loading">Loading adoptions...</p>
|
<p className="appt-loading">Loading adoptions...</p>
|
||||||
) : adoptions.length === 0 ? (
|
) : (() => {
|
||||||
<p className="appt-empty">No adoption requests yet.</p>
|
const activeAdoptions = adoptions.filter((a) => a.adoptionStatus?.toLowerCase() === "pending");
|
||||||
|
const pastAdoptions = adoptions.filter((a) => a.adoptionStatus?.toLowerCase() !== "pending");
|
||||||
|
const q = adoptionSearch.toLowerCase();
|
||||||
|
const filteredActive = activeAdoptions.filter((a) =>
|
||||||
|
!q || [a.petName, a.sourceStoreName].some((v) => v?.toLowerCase().includes(q))
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className="appt-search"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search adoptions…"
|
||||||
|
value={adoptionSearch}
|
||||||
|
onChange={(e) => setAdoptionSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
{filteredActive.length === 0 ? (
|
||||||
|
<p className="appt-empty">{activeAdoptions.length === 0 ? "No active adoption requests." : "No results."}</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="appt-list">
|
<div className="appt-list">
|
||||||
{adoptions.map((a) => (
|
{filteredActive.map((a) => (
|
||||||
<div key={a.adoptionId} className="appt-card">
|
<div key={a.adoptionId} className="appt-card">
|
||||||
<div className="appt-card-header">
|
<div className="appt-card-header">
|
||||||
<span className="appt-card-service">{a.petName}</span>
|
<span className="appt-card-service">{a.petName}</span>
|
||||||
@@ -1023,7 +1086,6 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
<span>{a.sourceStoreName}</span>
|
<span>{a.sourceStoreName}</span>
|
||||||
<span>{a.adoptionDate}</span>
|
<span>{a.adoptionDate}</span>
|
||||||
</div>
|
</div>
|
||||||
{a.adoptionStatus?.toLowerCase() === "pending" && (
|
|
||||||
<div className="appt-card-actions">
|
<div className="appt-card-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1034,11 +1096,38 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
|||||||
{cancellingId === a.adoptionId ? "Cancelling..." : "Cancel"}
|
{cancellingId === a.adoptionId ? "Cancelling..." : "Cancel"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{pastAdoptions.length > 0 && (
|
||||||
|
<div className="appt-past-section">
|
||||||
|
<button className="appt-past-toggle" onClick={() => setShowPastAdoptions((v) => !v)}>
|
||||||
|
{showPastAdoptions ? "Hide" : "Show"} past adoptions ({pastAdoptions.length})
|
||||||
|
</button>
|
||||||
|
{showPastAdoptions && (
|
||||||
|
<div className="appt-list appt-list--past">
|
||||||
|
{pastAdoptions.map((a) => (
|
||||||
|
<div key={a.adoptionId} className="appt-card appt-card--past">
|
||||||
|
<div className="appt-card-header">
|
||||||
|
<span className="appt-card-service">{a.petName}</span>
|
||||||
|
<span className={`appt-card-status appt-card-status--${a.adoptionStatus?.toLowerCase()}`}>
|
||||||
|
{a.adoptionStatus}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="appt-card-details">
|
||||||
|
<span>{a.sourceStoreName}</span>
|
||||||
|
<span>{a.adoptionDate}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export default function ContactPage() {
|
|||||||
|
|
||||||
async function handleSend(e) {
|
async function handleSend(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!token) {
|
||||||
|
setSendError("Please log in to send a message.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setSending(true);
|
setSending(true);
|
||||||
setSendError(null);
|
setSendError(null);
|
||||||
try {
|
try {
|
||||||
@@ -72,7 +76,6 @@ export default function ContactPage() {
|
|||||||
<p>Phone: (03) 9000 0000</p>
|
<p>Phone: (03) 9000 0000</p>
|
||||||
<p>Hours: Mon–Sat, 9:00 AM – 6:00 PM</p>
|
<p>Hours: Mon–Sat, 9:00 AM – 6:00 PM</p>
|
||||||
|
|
||||||
{token && (
|
|
||||||
<div className="contact-form-section">
|
<div className="contact-form-section">
|
||||||
<h3>Send Us a Message</h3>
|
<h3>Send Us a Message</h3>
|
||||||
{sendSuccess ? (
|
{sendSuccess ? (
|
||||||
@@ -109,7 +112,6 @@ export default function ContactPage() {
|
|||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="info-card">
|
<div className="info-card">
|
||||||
|
|||||||
@@ -713,7 +713,7 @@ body {
|
|||||||
.info-content {
|
.info-content {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 2rem 3rem;
|
padding: 0 2rem 1.5rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
@@ -737,6 +737,7 @@ body {
|
|||||||
padding-left: 1.2rem;
|
padding-left: 1.2rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
list-style-type: disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card-grid {
|
.info-card-grid {
|
||||||
@@ -1713,6 +1714,49 @@ body {
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appt-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-search:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #e68672;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-past-section {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-past-toggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #888;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-past-toggle:hover {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-list--past {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appt-card--past {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
/* Adoption Pet Selection */
|
/* Adoption Pet Selection */
|
||||||
|
|
||||||
.appt-adopt-grid {
|
.appt-adopt-grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user