Small UI changes
This commit is contained in:
22
src/components/Navigation.astro
Normal file
22
src/components/Navigation.astro
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
import SignOut from "./SignOut.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="fixed top-0 right-0 p-4 z-50">
|
||||||
|
<div class="dropdown dropdown-end">
|
||||||
|
<label tabindex="0" class="btn btn-square btn-ghost">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||||
|
</svg>
|
||||||
|
</label>
|
||||||
|
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-52">
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/rsvp">RSVP</a></li>
|
||||||
|
<li><a href="/registry">View Registry</a></li>
|
||||||
|
<li><a href="/faq">FAQ</a></li>
|
||||||
|
<li>
|
||||||
|
<SignOut />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -4,6 +4,7 @@ import "../styles/global.css";
|
|||||||
interface FormData {
|
interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
dietaryRestrictions: string;
|
dietaryRestrictions: string;
|
||||||
|
notes: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiResponse {
|
interface ApiResponse {
|
||||||
@ -15,6 +16,7 @@ const RSVPForm = () => {
|
|||||||
const [formData, setFormData] = useState<FormData>({
|
const [formData, setFormData] = useState<FormData>({
|
||||||
name: "",
|
name: "",
|
||||||
dietaryRestrictions: "",
|
dietaryRestrictions: "",
|
||||||
|
notes: "",
|
||||||
});
|
});
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -61,6 +63,7 @@ const RSVPForm = () => {
|
|||||||
setFormData({
|
setFormData({
|
||||||
name: "",
|
name: "",
|
||||||
dietaryRestrictions: "",
|
dietaryRestrictions: "",
|
||||||
|
notes: "",
|
||||||
});
|
});
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@ -163,6 +166,26 @@ const RSVPForm = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Notes Input */}
|
||||||
|
<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>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="notes"
|
||||||
|
value={formData.notes}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
notes: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
placeholder="e.g. only coming for ceremony, bringing a +1, etc."
|
||||||
|
className="textarea textarea-bordered h-24"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
71
src/components/RSVPManager.tsx
Normal file
71
src/components/RSVPManager.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import type { RSVPItem } from "../lib/types";
|
||||||
|
|
||||||
|
const RSVPManager = () => {
|
||||||
|
const [rsvpList, setRSVPList] = useState<RSVPItem[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchRSVPList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchRSVPList = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/rsvp");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setRSVPList(data);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e.message);
|
||||||
|
console.error("Failed to fetch RSVP list:", e);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="text-center">Loading RSVP list...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div className="text-red-500 text-center">Error: {error}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-4">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dietary Restrictions</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rsvpList.map((rsvp, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td>{rsvp.name}</td>
|
||||||
|
<td>{rsvp.dietaryRestrictions || "None"}</td>
|
||||||
|
<td>{(rsvp.notes && rsvp.notes !== "undefined") ? rsvp.notes.trim() : "None"}</td>
|
||||||
|
<td>{new Date(rsvp.timestamp).toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<p>Total RSVPs: {rsvpList.length}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RSVPManager;
|
9
src/components/SignOut.astro
Normal file
9
src/components/SignOut.astro
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<button class="btn btn-secondary" onclick="handleSignOut()">Sign Out</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function handleSignOut() {
|
||||||
|
sessionStorage.removeItem("isAuthenticated");
|
||||||
|
sessionStorage.removeItem("role");
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
|
import Navigation from "../components/Navigation.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -21,6 +22,7 @@ const { title } = Astro.props;
|
|||||||
<title>{title || "Ixabatasha"}</title>
|
<title>{title || "Ixabatasha"}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="flex items-center justify-center min-h-screen">
|
<body class="flex items-center justify-center min-h-screen">
|
||||||
|
<Navigation />
|
||||||
<slot />
|
<slot />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
import AuthLayout from "./AuthLayout.astro";
|
import AuthLayout from "./AuthLayout.astro";
|
||||||
|
import Navigation from "../components/Navigation.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -18,12 +19,16 @@ const { title } = Astro.props;
|
|||||||
rel="icon"
|
rel="icon"
|
||||||
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>❤️</text></svg>"
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>❤️</text></svg>"
|
||||||
/>
|
/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap" rel="stylesheet">
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<title>{title || "Ixabatasha"}</title>
|
<title>{title || "Ixabatasha"}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="flex items-center justify-center min-h-screen">
|
<body>
|
||||||
<AuthLayout title={title}>
|
<Navigation />
|
||||||
<slot />
|
<div class="flex items-center justify-center min-h-screen">
|
||||||
</AuthLayout>
|
<AuthLayout title={title}>
|
||||||
|
<slot />
|
||||||
|
</AuthLayout>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -9,5 +9,6 @@ export interface RegistryItem {
|
|||||||
export interface RSVPItem {
|
export interface RSVPItem {
|
||||||
name: string;
|
name: string;
|
||||||
dietaryRestrictions: string;
|
dietaryRestrictions: string;
|
||||||
|
notes: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ import { getS3Data, putS3Data } from "../../lib/s3";
|
|||||||
import type { RSVPItem } from "../../lib/types";
|
import type { RSVPItem } from "../../lib/types";
|
||||||
|
|
||||||
const objectsToCSV = (data: RSVPItem[]): string => {
|
const objectsToCSV = (data: RSVPItem[]): string => {
|
||||||
const headers = ["name", "dietaryRestrictions", "timestamp"];
|
const headers = ["name", "dietaryRestrictions", "notes", "timestamp"];
|
||||||
const csvRows = [headers.join(",")];
|
const csvRows = [headers.join(",")];
|
||||||
|
|
||||||
data.forEach((entry) => {
|
data.forEach((entry) => {
|
||||||
@ -27,7 +27,12 @@ const csvToObjects = (csv: string): RSVPItem[] => {
|
|||||||
.filter((line) => line.trim())
|
.filter((line) => line.trim())
|
||||||
.map((line) => {
|
.map((line) => {
|
||||||
const values = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
const values = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
||||||
const entry: Partial<RSVPItem> = {};
|
const entry: Partial<RSVPItem> = {
|
||||||
|
name: "",
|
||||||
|
dietaryRestrictions: "",
|
||||||
|
notes: "",
|
||||||
|
timestamp: "",
|
||||||
|
};
|
||||||
|
|
||||||
headers.forEach((header, index) => {
|
headers.forEach((header, index) => {
|
||||||
let value = values[index] || "";
|
let value = values[index] || "";
|
||||||
@ -39,6 +44,44 @@ const csvToObjects = (csv: string): RSVPItem[] => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// GET: Retrieve all RSVPs
|
||||||
|
export const GET: APIRoute = async ({ request }) => {
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const FILE_KEY = "rsvp.csv";
|
||||||
|
const fileContent = await getS3Data<string>(FILE_KEY);
|
||||||
|
|
||||||
|
if (!fileContent) {
|
||||||
|
return new Response(JSON.stringify([]), {
|
||||||
|
status: 200,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsvpList = csvToObjects(fileContent);
|
||||||
|
return new Response(JSON.stringify(rsvpList), {
|
||||||
|
status: 200,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error retrieving RSVPs:", error);
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: "Failed to retrieve RSVPs",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// POST: Submit a new RSVP
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
console.log("API endpoint hit - starting request processing");
|
console.log("API endpoint hit - starting request processing");
|
||||||
|
|
||||||
@ -65,6 +108,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
existingRsvps.push({
|
existingRsvps.push({
|
||||||
...newRsvp,
|
...newRsvp,
|
||||||
|
notes: newRsvp.notes || "",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
118
src/pages/faq.astro
Normal file
118
src/pages/faq.astro
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="FAQ">
|
||||||
|
<div class="flex flex-col gap-8 max-w-3xl mx-auto p-6">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-4xl mb-8">Frequently Asked Questions</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Dress Code -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Dress Code?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<p class="text-lg">
|
||||||
|
Semi-formal, any colour you want. Want to get dressed up? Go for it! Want to wear a shirt with no stains on it? Go for it!
|
||||||
|
There will be dancing, so choose your wardrobe accordingly. Maybe don't wear a white dress...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dietary Restrictions -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Dietary Restrictions?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<p class="text-lg">
|
||||||
|
We will do our best to accommodate any dietary restrictions you have! Our caterer can cook gluten/dairy/peanut free etc.
|
||||||
|
but may not be able to guarantee that there are no traces of allergens in the food. Please contact us if you have any concerns.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alcohol -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Alcohol?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<p class="text-lg">
|
||||||
|
We will not be serving alcohol.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Childcare -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Childcare?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<p class="text-lg">
|
||||||
|
Your young children are adorable and welcome, but we don't have childcare or designated areas for naps etc.
|
||||||
|
If you have any concerns, please contact us.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gifts -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Gifts?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<p class="text-lg">
|
||||||
|
The best gift is you sharing in our joy! Don't feel obligated. If you would like to give a physical gift,
|
||||||
|
cash is preferred, but we do have a registry available on this website as well.
|
||||||
|
Ix would be over the moon if someone found him a
|
||||||
|
<a href="https://www.youtube.com/watch?v=jeT7X3HfXP8" target="_blank" class="link link-primary">hurdy-gurdy</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Help -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Can I Help With Anything?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<p class="text-lg">
|
||||||
|
If you're up for staying a bit later, we could definitely use your help cleaning up the venues when the wedding is done!
|
||||||
|
Tony and Gladys will be coordinating that the day-of, please speak with them. If you want to help with wedding preparations,
|
||||||
|
please contact Ix or Natasha. Thank you!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200">
|
||||||
|
<input type="checkbox" />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
Where Do I Contact You With All of My Concerns?
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<div class="text-lg space-y-2">
|
||||||
|
<p>Email us at: <a href="mailto:ixabatasha25@proton.me" class="link link-primary">ixabatasha25@proton.me</a></p>
|
||||||
|
<p>Call/text Natasha: <a href="tel:13062929000" class="link link-primary">1 (306) 292-9000</a></p>
|
||||||
|
<p>Call/text Ix: <a href="tel:13067179403" class="link link-primary">1 (306) 717-9403</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-8">
|
||||||
|
<a href="/" class="btn btn-primary">Back to Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
@ -1,16 +1,63 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import SignOut from "../components/SignOut.tsx";
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Welcome">
|
<Layout title="Welcome">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-8 max-w-5xl mx-auto p-6">
|
||||||
<div class="text-center">❤️ Natasha + Ixabat ❤️</div>
|
<!-- Header -->
|
||||||
|
<div class="text-center space-y-4">
|
||||||
|
<div class="text-5xl font-normal" style="font-family: 'Great Vibes', cursive;">❤️ Natasha + Ixabat ❤️</div>
|
||||||
|
<p class="text-xl text-gray-600">We hope you can join us in celebration on</p>
|
||||||
|
<p class="text-2xl font-semibold">Saturday, June 7, 2025</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row gap-2 justify-center items-center">
|
<!-- Event Details Cards -->
|
||||||
<a class="btn btn-primary" href="/rsvp">RSVP</a>
|
<div class="flex flex-col md:flex-row gap-6">
|
||||||
<a class="btn btn-primary" href="/registry">View Registry</a>
|
<!-- Ceremony Card -->
|
||||||
<SignOut client:load />
|
<div class="card bg-base-100 shadow-xl flex-1">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl justify-center">Ceremony</h2>
|
||||||
|
<div class="text-center space-y-2">
|
||||||
|
<p class="text-xl">1:00 PM</p>
|
||||||
|
<p>Preston Avenue Community Church</p>
|
||||||
|
<p class="text-gray-600">2216 Preston Avenue, Saskatoon, SK</p>
|
||||||
|
<a
|
||||||
|
href="https://maps.google.com/?q=2216+Preston+Avenue+Saskatoon+SK"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-outline btn-sm mt-2"
|
||||||
|
>
|
||||||
|
View on Map
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reception Card -->
|
||||||
|
<div class="card bg-base-100 shadow-xl flex-1">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl justify-center">Reception</h2>
|
||||||
|
<div class="text-center space-y-2">
|
||||||
|
<p class="text-xl">5:00 PM</p>
|
||||||
|
<p>Saskatoon Christian School</p>
|
||||||
|
<p class="text-gray-600">55 Glazier Road, Corman Park, SK</p>
|
||||||
|
<a
|
||||||
|
href="https://maps.google.com/?q=55+Glazier+Road+Corman+Park+SK"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-outline btn-sm mt-2"
|
||||||
|
>
|
||||||
|
View on Map
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RSVP Section -->
|
||||||
|
<div class="text-center space-y-4">
|
||||||
|
<p class="text-lg">Please RSVP whether you're able to come or not by <span class="font-semibold">April 1</span></p>
|
||||||
|
<a href="/rsvp" class="btn btn-primary btn-lg">
|
||||||
|
RSVP Now
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -18,8 +18,7 @@ import SignOut from "../../components/SignOut.tsx";
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-row gap-2 justify-center items-center">
|
<div class="flex flex-row gap-2 justify-center items-center">
|
||||||
<a class="btn btn-primary" href="/">Home</a>
|
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||||
<SignOut client:load />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AdminLayout>
|
</AdminLayout>
|
||||||
|
@ -13,8 +13,7 @@ import SignOut from "../../components/SignOut.tsx";
|
|||||||
<RegistryList client:load />
|
<RegistryList client:load />
|
||||||
|
|
||||||
<div class="flex flex-row gap-2 justify-center items-center">
|
<div class="flex flex-row gap-2 justify-center items-center">
|
||||||
<a class="btn btn-primary" href="/">Home</a>
|
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||||
<SignOut client:load />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
@ -13,8 +13,7 @@ import SignOut from "../components/SignOut.tsx";
|
|||||||
<RSVP client:load />
|
<RSVP client:load />
|
||||||
|
|
||||||
<div class="flex flex-row gap-2 justify-center items-center">
|
<div class="flex flex-row gap-2 justify-center items-center">
|
||||||
<a class="btn btn-primary" href="/">Home</a>
|
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||||
<SignOut client:load />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
54
src/pages/rsvp/admin.astro
Normal file
54
src/pages/rsvp/admin.astro
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
import AdminLayout from "../../layouts/AdminLayout.astro";
|
||||||
|
import RSVPManager from "../../components/RSVPManager.tsx";
|
||||||
|
import SignIn from "../../components/SignIn.tsx";
|
||||||
|
import SignOut from "../../components/SignOut.tsx";
|
||||||
|
---
|
||||||
|
|
||||||
|
<AdminLayout title="RSVP Manager">
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="text-center text-4xl">RSVP Manager</div>
|
||||||
|
|
||||||
|
<div id="auth-container">
|
||||||
|
<SignIn client:load onSuccess={() => {}} requiredRole="admin" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="manager-container" class="hidden">
|
||||||
|
<RSVPManager client:load />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-2 justify-center items-center">
|
||||||
|
<a class="btn btn-primary" href="/">Back to Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AdminLayout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const checkAndUpdateVisibility = (role: string | null) => {
|
||||||
|
if (role === "admin") {
|
||||||
|
document.getElementById("auth-container")?.classList.add("hidden");
|
||||||
|
document
|
||||||
|
.getElementById("manager-container")
|
||||||
|
?.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
document
|
||||||
|
.getElementById("auth-container")
|
||||||
|
?.classList.remove("hidden");
|
||||||
|
document
|
||||||
|
.getElementById("manager-container")
|
||||||
|
?.classList.add("hidden");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check auth state on page load
|
||||||
|
const isAuthenticated =
|
||||||
|
sessionStorage.getItem("isAuthenticated") === "true";
|
||||||
|
const role = sessionStorage.getItem("role");
|
||||||
|
checkAndUpdateVisibility(role);
|
||||||
|
|
||||||
|
// Add event listener for custom event from SignIn component
|
||||||
|
document.addEventListener("auth-success", ((event: CustomEvent) => {
|
||||||
|
const newRole = event.detail?.role || sessionStorage.getItem("role");
|
||||||
|
checkAndUpdateVisibility(newRole);
|
||||||
|
}) as EventListener);
|
||||||
|
</script>
|
Reference in New Issue
Block a user