Page updates

This commit is contained in:
2025-02-25 14:27:07 -06:00
parent 1b7904afbd
commit 042fe6cc96
4 changed files with 107 additions and 77 deletions

View File

@ -4,6 +4,7 @@ import "../styles/global.css";
interface FormData {
name: string;
attending: boolean | null;
dietaryRestrictions: string;
notes: string;
}
@ -16,6 +17,7 @@ interface ApiResponse {
const RSVPForm = () => {
const [formData, setFormData] = useState<FormData>({
name: "",
attending: null,
dietaryRestrictions: "",
notes: "",
});
@ -23,7 +25,7 @@ const RSVPForm = () => {
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const isFormValid = formData.name.trim().length > 0;
const isFormValid = formData.name.trim().length > 0 && formData.attending !== null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@ -34,9 +36,8 @@ const RSVPForm = () => {
setSuccess(false);
try {
// Create an AbortController for timeout
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
const timeoutId = setTimeout(() => controller.abort(), 10000);
const response = await fetchWithAuth("/api/rsvp", {
method: "POST",
@ -63,6 +64,7 @@ const RSVPForm = () => {
setFormData({
name: "",
attending: null,
dietaryRestrictions: "",
notes: "",
});
@ -80,7 +82,6 @@ const RSVPForm = () => {
}
};
return (
<div className="max-w-2xl mx-auto p-6">
<div className="card bg-base-100 shadow-xl">
@ -147,30 +148,68 @@ const RSVPForm = () => {
/>
</div>
{/* Dietary Restrictions Input */}
{/* Attendance Selection */}
<div className="flex flex-col gap-2">
<label htmlFor="dietary" className="label">
<span className="label-text">Dietary Restrictions</span>
<label className="label">
<span className="label-text">Will you be attending? *</span>
</label>
<textarea
id="dietary"
value={formData.dietaryRestrictions}
onChange={(e) =>
setFormData((prev) => ({
...prev,
dietaryRestrictions: e.target.value,
}))
}
placeholder="Please list any dietary restrictions or allergies"
className="textarea textarea-bordered h-24"
disabled={isSubmitting}
/>
<div className="flex gap-4">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
className="radio radio-primary"
checked={formData.attending === true}
onChange={() => setFormData(prev => ({ ...prev, attending: true }))}
disabled={isSubmitting}
/>
<span>Yes, I'll be there!</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
className="radio radio-primary"
checked={formData.attending === false}
onChange={() => setFormData(prev => ({ ...prev, attending: false }))}
disabled={isSubmitting}
/>
<span>No, I can't make it</span>
</label>
</div>
</div>
{/* Notes Input */}
{/* Only show these fields if attending */}
{formData.attending && (
<>
{/* Dietary Restrictions Input */}
<div className="flex flex-col gap-2">
<label htmlFor="dietary" className="label">
<span className="label-text">Dietary Restrictions</span>
</label>
<textarea
id="dietary"
value={formData.dietaryRestrictions}
onChange={(e) =>
setFormData((prev) => ({
...prev,
dietaryRestrictions: e.target.value,
}))
}
placeholder="Please list any dietary restrictions or allergies"
className="textarea textarea-bordered h-24"
disabled={isSubmitting}
/>
</div>
</>
)}
{/* Notes Input - shown for both attending and not attending */}
<div className="flex flex-col gap-2">
<label htmlFor="notes" className="label">
<span className="label-text">Anything else you'd like Ix and Tasha to know?</span>
<span className="label-text">
{formData.attending
? "Anything else you'd like Ix and Tasha to know?"
: "Would you like to leave a message for Ix and Tasha?"}
</span>
</label>
<textarea
id="notes"
@ -181,7 +220,9 @@ const RSVPForm = () => {
notes: e.target.value,
}))
}
placeholder="e.g. only coming for ceremony, bringing a +1, etc."
placeholder={formData.attending
? "e.g. attending just the ceremony or reception, etc."
: "Optional message"}
className="textarea textarea-bordered h-24"
disabled={isSubmitting}
/>
@ -190,7 +231,7 @@ const RSVPForm = () => {
{/* Submit Button */}
<button
type="submit"
className="btn btn-primary w-full"
className={`btn w-full ${formData.attending ? 'btn-primary' : 'btn-secondary'}`}
disabled={!isFormValid || isSubmitting}
>
{isSubmitting ? (
@ -198,8 +239,12 @@ const RSVPForm = () => {
<span className="loading loading-spinner"></span>
Submitting...
</>
) : formData.attending ? (
"Yes, I'll Be There!"
) : formData.attending === false ? (
"Send My Regrets"
) : (
"I'll Be There!"
"Submit RSVP"
)}
</button>
</form>

View File

@ -55,13 +55,37 @@ const RSVPManager = () => {
return <div className="text-red-500 text-center">Error: {error}</div>;
}
const attending = rsvpList.filter(rsvp => rsvp.attending === true);
const notAttending = rsvpList.filter(rsvp => rsvp.attending === false);
const noResponse = rsvpList.filter(rsvp => rsvp.attending === undefined || rsvp.attending === null);
return (
<div className="container mx-auto p-4">
<div className="stats shadow mb-8 w-full">
<div className="stat">
<div className="stat-title">Total RSVPs</div>
<div className="stat-value">{rsvpList.length}</div>
</div>
<div className="stat">
<div className="stat-title">Attending</div>
<div className="stat-value text-success">{attending.length}</div>
</div>
<div className="stat">
<div className="stat-title">Not Attending</div>
<div className="stat-value text-error">{notAttending.length}</div>
</div>
<div className="stat">
<div className="stat-title">No Response</div>
<div className="stat-value text-warning">{noResponse.length}</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="table w-full">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Dietary Restrictions</th>
<th>Notes</th>
<th>Timestamp</th>
@ -71,7 +95,18 @@ const RSVPManager = () => {
{rsvpList.map((rsvp, index) => (
<tr key={index}>
<td>{rsvp.name}</td>
<td>{rsvp.dietaryRestrictions || "None"}</td>
<td>
<div className={`badge ${
rsvp.attending === true ? 'badge-success' :
rsvp.attending === false ? 'badge-error' :
'badge-warning'
}`}>
{rsvp.attending === true ? 'Attending' :
rsvp.attending === false ? 'Not Attending' :
'No Response'}
</div>
</td>
<td>{rsvp.attending ? (rsvp.dietaryRestrictions || "None") : "-"}</td>
<td>{(rsvp.notes && rsvp.notes !== "undefined") ? rsvp.notes.trim() : "None"}</td>
<td>{new Date(rsvp.timestamp).toLocaleString()}</td>
</tr>
@ -79,10 +114,6 @@ const RSVPManager = () => {
</tbody>
</table>
</div>
<div className="mt-4 text-center">
<p>Total RSVPs: {rsvpList.length}</p>
</div>
</div>
);
};

View File

@ -8,6 +8,7 @@ export interface RegistryItem {
export interface RSVPItem {
name: string;
attending: boolean | null;
dietaryRestrictions: string;
notes: string;
timestamp: string;

View File

@ -1,47 +0,0 @@
---
import Layout from "../layouts/Layout.astro";
import RSVP from "../components/RSVP.tsx";
import SignIn from "../components/SignIn.tsx";
---
<Layout title="RSVP">
<div class="flex flex-col gap-4">
<div class="text-center text-4xl">
Please RSVP using the form below:
</div>
<div id="auth-container">
<SignIn client:load onSuccess={() => {}} requiredRole="guest" />
</div>
<div id="content-container" class="hidden">
<RSVP client:load />
<div class="flex flex-row gap-2 justify-center items-center mt-4">
<a class="btn btn-primary" href="/">Back to Home</a>
</div>
</div>
</div>
</Layout>
<script>
import { hasRole, isAuthenticated } from "../utils/auth-client";
function updateVisibility() {
const authContainer = document.getElementById("auth-container");
const contentContainer = document.getElementById("content-container");
if (isAuthenticated() && hasRole("guest")) {
authContainer?.classList.add("hidden");
contentContainer?.classList.remove("hidden");
} else {
authContainer?.classList.remove("hidden");
contentContainer?.classList.add("hidden");
}
}
// Check auth state on page load
updateVisibility();
// Add event listener for custom event from SignIn component
document.addEventListener("auth-success", updateVisibility);
</script>