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 { interface FormData {
name: string; name: string;
attending: boolean | null;
dietaryRestrictions: string; dietaryRestrictions: string;
notes: string; notes: string;
} }
@ -16,6 +17,7 @@ interface ApiResponse {
const RSVPForm = () => { const RSVPForm = () => {
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<FormData>({
name: "", name: "",
attending: null,
dietaryRestrictions: "", dietaryRestrictions: "",
notes: "", notes: "",
}); });
@ -23,7 +25,7 @@ const RSVPForm = () => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false); 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) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@ -34,9 +36,8 @@ const RSVPForm = () => {
setSuccess(false); setSuccess(false);
try { try {
// Create an AbortController for timeout
const controller = new AbortController(); 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", { const response = await fetchWithAuth("/api/rsvp", {
method: "POST", method: "POST",
@ -63,6 +64,7 @@ const RSVPForm = () => {
setFormData({ setFormData({
name: "", name: "",
attending: null,
dietaryRestrictions: "", dietaryRestrictions: "",
notes: "", notes: "",
}); });
@ -80,7 +82,6 @@ const RSVPForm = () => {
} }
}; };
return ( return (
<div className="max-w-2xl mx-auto p-6"> <div className="max-w-2xl mx-auto p-6">
<div className="card bg-base-100 shadow-xl"> <div className="card bg-base-100 shadow-xl">
@ -147,30 +148,68 @@ const RSVPForm = () => {
/> />
</div> </div>
{/* Dietary Restrictions Input */} {/* Attendance Selection */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label htmlFor="dietary" className="label"> <label className="label">
<span className="label-text">Dietary Restrictions</span> <span className="label-text">Will you be attending? *</span>
</label> </label>
<textarea <div className="flex gap-4">
id="dietary" <label className="flex items-center gap-2 cursor-pointer">
value={formData.dietaryRestrictions} <input
onChange={(e) => type="radio"
setFormData((prev) => ({ className="radio radio-primary"
...prev, checked={formData.attending === true}
dietaryRestrictions: e.target.value, onChange={() => setFormData(prev => ({ ...prev, attending: true }))}
})) disabled={isSubmitting}
} />
placeholder="Please list any dietary restrictions or allergies" <span>Yes, I'll be there!</span>
className="textarea textarea-bordered h-24" </label>
disabled={isSubmitting} <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> </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"> <div className="flex flex-col gap-2">
<label htmlFor="notes" className="label"> <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> </label>
<textarea <textarea
id="notes" id="notes"
@ -181,7 +220,9 @@ const RSVPForm = () => {
notes: e.target.value, 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" className="textarea textarea-bordered h-24"
disabled={isSubmitting} disabled={isSubmitting}
/> />
@ -190,7 +231,7 @@ const RSVPForm = () => {
{/* Submit Button */} {/* Submit Button */}
<button <button
type="submit" type="submit"
className="btn btn-primary w-full" className={`btn w-full ${formData.attending ? 'btn-primary' : 'btn-secondary'}`}
disabled={!isFormValid || isSubmitting} disabled={!isFormValid || isSubmitting}
> >
{isSubmitting ? ( {isSubmitting ? (
@ -198,8 +239,12 @@ const RSVPForm = () => {
<span className="loading loading-spinner"></span> <span className="loading loading-spinner"></span>
Submitting... Submitting...
</> </>
) : formData.attending ? (
"Yes, I'll Be There!"
) : formData.attending === false ? (
"Send My Regrets"
) : ( ) : (
"I'll Be There!" "Submit RSVP"
)} )}
</button> </button>
</form> </form>

View File

@ -55,13 +55,37 @@ const RSVPManager = () => {
return <div className="text-red-500 text-center">Error: {error}</div>; 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 ( return (
<div className="container mx-auto p-4"> <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"> <div className="overflow-x-auto">
<table className="table w-full"> <table className="table w-full">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Status</th>
<th>Dietary Restrictions</th> <th>Dietary Restrictions</th>
<th>Notes</th> <th>Notes</th>
<th>Timestamp</th> <th>Timestamp</th>
@ -71,7 +95,18 @@ const RSVPManager = () => {
{rsvpList.map((rsvp, index) => ( {rsvpList.map((rsvp, index) => (
<tr key={index}> <tr key={index}>
<td>{rsvp.name}</td> <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>{(rsvp.notes && rsvp.notes !== "undefined") ? rsvp.notes.trim() : "None"}</td>
<td>{new Date(rsvp.timestamp).toLocaleString()}</td> <td>{new Date(rsvp.timestamp).toLocaleString()}</td>
</tr> </tr>
@ -79,10 +114,6 @@ const RSVPManager = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
<div className="mt-4 text-center">
<p>Total RSVPs: {rsvpList.length}</p>
</div>
</div> </div>
); );
}; };

View File

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