This commit is contained in:
2025-02-25 20:52:47 -06:00
parent 042fe6cc96
commit afb609a95a
9 changed files with 336 additions and 149 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

BIN
public/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

View File

@ -44,7 +44,11 @@ const RSVPForm = () => {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify(formData), body: JSON.stringify({
...formData,
attending: formData.attending === true, // Ensure it's a boolean
timestamp: new Date().toISOString(),
}),
signal: controller.signal, signal: controller.signal,
}); });
@ -242,7 +246,7 @@ const RSVPForm = () => {
) : formData.attending ? ( ) : formData.attending ? (
"Yes, I'll Be There!" "Yes, I'll Be There!"
) : formData.attending === false ? ( ) : formData.attending === false ? (
"Send My Regrets" "Submit RSVP"
) : ( ) : (
"Submit RSVP" "Submit RSVP"
)} )}

View File

@ -33,6 +33,31 @@ const RSVPManager = () => {
} }
}; };
const handleDeleteRSVP = async (name: string, timestamp: string) => {
if (!confirm(`Are you sure you want to delete the RSVP from ${name}?`)) {
return;
}
try {
const response = await fetchWithAuth(`/api/rsvp`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name, timestamp }),
});
if (!response.ok) {
throw new Error(`Failed to delete RSVP: ${response.statusText}`);
}
await fetchRSVPList();
} catch (error: any) {
setError(error.message);
console.error("Error deleting RSVP:", error);
}
};
useEffect(() => { useEffect(() => {
fetchRSVPList(); fetchRSVPList();
@ -57,30 +82,27 @@ const RSVPManager = () => {
const attending = rsvpList.filter(rsvp => rsvp.attending === true); const attending = rsvpList.filter(rsvp => rsvp.attending === true);
const notAttending = rsvpList.filter(rsvp => rsvp.attending === false); 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="flex flex-col gap-4 mx-auto p-4">
<div className="stats shadow mb-8 w-full"> <div className="stats shadow mb-8 mx-auto">
<div className="stat"> <div className="stat">
<div className="stat-title">Total RSVPs</div> <div className="stat-title">Total Responses</div>
<div className="stat-value">{rsvpList.length}</div> <div className="stat-value">{rsvpList.length}</div>
</div> </div>
<div className="stat"> <div className="stat">
<div className="stat-title">Attending</div> <div className="stat-title">Attending</div>
<div className="stat-value text-success">{attending.length}</div> <div className="stat-value text-success">{attending.length}</div>
<div className="stat-desc">Will be there</div>
</div> </div>
<div className="stat"> <div className="stat">
<div className="stat-title">Not Attending</div> <div className="stat-title">Not Attending</div>
<div className="stat-value text-error">{notAttending.length}</div> <div className="stat-value text-error">{notAttending.length}</div>
</div> <div className="stat-desc">Can't make it</div>
<div className="stat">
<div className="stat-title">No Response</div>
<div className="stat-value text-warning">{noResponse.length}</div>
</div> </div>
</div> </div>
<div className="overflow-x-auto"> <div className="overflow-x-auto mx-auto">
<table className="table w-full"> <table className="table w-full">
<thead> <thead>
<tr> <tr>
@ -88,7 +110,8 @@ const RSVPManager = () => {
<th>Status</th> <th>Status</th>
<th>Dietary Restrictions</th> <th>Dietary Restrictions</th>
<th>Notes</th> <th>Notes</th>
<th>Timestamp</th> <th>Response Date</th>
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -96,19 +119,29 @@ const RSVPManager = () => {
<tr key={index}> <tr key={index}>
<td>{rsvp.name}</td> <td>{rsvp.name}</td>
<td> <td>
<div className={`badge ${ <span className={`badge badge-lg ${
rsvp.attending === true ? 'badge-success' : rsvp.attending ? 'badge-success' : 'badge-error'
rsvp.attending === false ? 'badge-error' : } whitespace-nowrap`}>
'badge-warning' {rsvp.attending ? 'Attending' : 'Not Attending'}
}`}> </span>
{rsvp.attending === true ? 'Attending' : </td>
rsvp.attending === false ? 'Not Attending' : <td>{rsvp.dietaryRestrictions && rsvp.dietaryRestrictions !== "undefined" ? rsvp.dietaryRestrictions : "-"}</td>
'No Response'} <td>{rsvp.notes && rsvp.notes !== "undefined" ? rsvp.notes : "-"}</td>
</div> <td className="whitespace-nowrap">{new Date(rsvp.timestamp).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}</td>
<td>
<button
onClick={() => handleDeleteRSVP(rsvp.name, rsvp.timestamp)}
className="btn btn-error btn-sm"
>
Delete
</button>
</td> </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> </tr>
))} ))}
</tbody> </tbody>

View File

@ -4,10 +4,11 @@ import { fetchWithAuth, getAuthToken } from "../utils/auth-client";
const RegistryList = () => { const RegistryList = () => {
const [registryItems, setRegistryItems] = useState<RegistryItem[]>([]); const [registryItems, setRegistryItems] = useState<RegistryItem[]>([]);
const [claimedItems, setClaimedItems] = useState<Set<string>>(new Set());
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [claimantName, setClaimantName] = useState(""); const [claimantName, setClaimantName] = useState("");
const [claimedItems, setClaimedItems] = useState<Set<string>>(new Set()); const [showClaimants, setShowClaimants] = useState<Set<string>>(new Set());
const fetchRegistryItems = async () => { const fetchRegistryItems = async () => {
// Don't try to fetch if we don't have a token yet // Don't try to fetch if we don't have a token yet
@ -52,7 +53,7 @@ const RegistryList = () => {
return () => document.removeEventListener("auth-success", handleAuthSuccess); return () => document.removeEventListener("auth-success", handleAuthSuccess);
}, []); }, []);
const handleItemClick = (itemId: string) => { const handleCheckboxChange = (itemId: string) => {
const newClaimedItems = new Set(claimedItems); const newClaimedItems = new Set(claimedItems);
if (newClaimedItems.has(itemId)) { if (newClaimedItems.has(itemId)) {
newClaimedItems.delete(itemId); newClaimedItems.delete(itemId);
@ -62,6 +63,16 @@ const RegistryList = () => {
setClaimedItems(newClaimedItems); setClaimedItems(newClaimedItems);
}; };
const toggleClaimantVisibility = (itemId: string) => {
const newShowClaimants = new Set(showClaimants);
if (newShowClaimants.has(itemId)) {
newShowClaimants.delete(itemId);
} else {
newShowClaimants.add(itemId);
}
setShowClaimants(newShowClaimants);
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!claimantName.trim()) { if (!claimantName.trim()) {
setError("Please enter your name before claiming items"); setError("Please enter your name before claiming items");
@ -109,89 +120,110 @@ const RegistryList = () => {
} }
if (error) { if (error) {
if (error === "No authentication token found") {
return <div className="text-center">Initializing...</div>;
}
return <div className="text-red-500 text-center">Error: {error}</div>; return <div className="text-red-500 text-center">Error: {error}</div>;
} }
const availableItems = registryItems.filter((item) => !item.taken);
const claimedItemsArray = registryItems.filter((item) => item.taken);
return ( return (
<div className="container mx-auto p-4"> <div className="container mx-auto p-4">
{availableItems.length > 0 && ( <h1 className="text-2xl font-bold mb-4">Wedding Registry</h1>
<> <div className="overflow-x-auto">
<div className="mb-4"> <table className="table w-full">
<input <thead>
type="text" <tr>
value={claimantName} <th>Item</th>
onChange={(e) => setClaimantName(e.target.value)} <th>Link</th>
placeholder="Enter your name" <th>Status</th>
className="input input-bordered w-full max-w-xs" <th>Claim</th>
/> </tr>
</div> </thead>
<tbody>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8"> {registryItems.map((item) => (
{availableItems.map((item) => ( <tr key={item.id}>
<div <td>
key={item.id} <div className="flex flex-col gap-1">
className={`card bg-base-100 shadow-xl cursor-pointer ${ <div className="flex items-center gap-2">
claimedItems.has(item.id) {item.name}
? "border-4 border-primary" {item.taken && (
: "" <div className="flex items-center gap-2">
}`} <span className="badge badge-success">Taken</span>
onClick={() => handleItemClick(item.id)} <button
onClick={() => toggleClaimantVisibility(item.id)}
className="btn btn-xs btn-ghost"
> >
<div className="card-body"> {showClaimants.has(item.id) ? "Hide" : "Show"}
<h2 className="card-title">{item.name}</h2> </button>
</div>
)}
</div>
{item.taken &&
item.claimedBy &&
showClaimants.has(item.id) && (
<div className="text-sm text-gray-500 italic">
Claimed by: {item.claimedBy}
</div>
)}
</div>
</td>
<td>
{item.link && ( {item.link && (
<a <a
href={item.link} href={item.link}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="link link-primary" className="link link-primary"
onClick={(e) => e.stopPropagation()}
> >
View Item View
</a> </a>
)} )}
</div> </td>
</div> <td>{item.taken ? "Claimed" : "Available"}</td>
<td>
{!item.taken ? (
<input
type="checkbox"
className="checkbox"
checked={claimedItems.has(item.id)}
onChange={() => handleCheckboxChange(item.id)}
/>
) : <input
type="checkbox"
className="checkbox"
disabled
checked={true}
/>}
</td>
</tr>
))} ))}
</tbody>
</table>
</div> </div>
{claimedItems.size > 0 && ( {claimedItems.size > 0 && (
<div className="text-center mb-8"> <div className="mt-4 flex flex-col gap-4">
<div className="form-control">
<label className="label">
<span className="label-text">Your Name</span>
</label>
<input
type="text"
className="input input-bordered"
value={claimantName}
onChange={(e) => setClaimantName(e.target.value)}
placeholder="Enter your name"
required
/>
</div>
<button <button
onClick={handleSubmit}
className="btn btn-primary" className="btn btn-primary"
onClick={handleSubmit}
disabled={!claimantName.trim()} disabled={!claimantName.trim()}
> >
Claim Selected Items Submit Claims
</button> </button>
</div> </div>
)} )}
</>
)}
{claimedItemsArray.length > 0 && (
<div className="mt-8">
<h2 className="text-2xl font-bold mb-4">Already Claimed</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{claimedItemsArray.map((item) => (
<div
key={item.id}
className="card bg-base-100 shadow-xl opacity-50"
>
<div className="card-body">
<h2 className="card-title">{item.name}</h2>
<p className="text-sm">
Claimed by: {item.claimedBy}
</p>
</div>
</div>
))}
</div>
</div>
)}
</div> </div>
); );
}; };

View File

@ -88,6 +88,27 @@ const RegistryManager = () => {
} }
}; };
const handleReleaseClaim = async (id: string) => {
try {
const response = await fetchWithAuth(`/api/registry/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ taken: false, claimedBy: null }),
});
if (!response.ok) {
throw new Error(`Failed to release claim: ${response.statusText}`);
}
await fetchRegistryItems();
} catch (error: any) {
setError(error.message);
console.error("Error releasing claim:", error);
}
};
const toggleShowClaimant = (id: string) => { const toggleShowClaimant = (id: string) => {
const newShowClaimants = new Set(showClaimants); const newShowClaimants = new Set(showClaimants);
if (newShowClaimants.has(id)) { if (newShowClaimants.has(id)) {
@ -111,27 +132,33 @@ const RegistryManager = () => {
return ( return (
<div className="container mx-auto p-4"> <div className="container mx-auto p-4">
<form onSubmit={handleAddItem} className="mb-8"> <form onSubmit={handleAddItem} className="mb-8 max-w-2xl mx-auto">
<div className="flex flex-col gap-4"> <div className="grid grid-cols-1 gap-4">
<div className="form-control w-full">
<input <input
type="text" type="text"
value={newItem.name} value={newItem.name}
onChange={(e) => setNewItem({ ...newItem, name: e.target.value })} onChange={(e) => setNewItem({ ...newItem, name: e.target.value })}
placeholder="Item name" placeholder="Item name"
className="input input-bordered" className="input input-bordered w-full"
required required
/> />
</div>
<div className="form-control w-full">
<input <input
type="url" type="url"
value={newItem.link} value={newItem.link}
onChange={(e) => setNewItem({ ...newItem, link: e.target.value })} onChange={(e) => setNewItem({ ...newItem, link: e.target.value })}
placeholder="Item link (optional)" placeholder="Item link (optional)"
className="input input-bordered" className="input input-bordered w-full"
/> />
<button type="submit" className="btn btn-primary"> </div>
<div className="form-control w-full">
<button type="submit" className="btn btn-primary w-full">
Add Item Add Item
</button> </button>
</div> </div>
</div>
</form> </form>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@ -185,12 +212,22 @@ const RegistryManager = () => {
)} )}
</td> </td>
<td> <td>
<div className="flex gap-2">
{item.taken && (
<button
onClick={() => handleReleaseClaim(item.id)}
className="btn btn-warning btn-sm"
>
Release Claim
</button>
)}
<button <button
onClick={() => handleDeleteItem(item.id)} onClick={() => handleDeleteItem(item.id)}
className="btn btn-error btn-sm" className="btn btn-error btn-sm"
> >
Delete Delete
</button> </button>
</div>
</td> </td>
</tr> </tr>
))} ))}

View File

@ -4,13 +4,21 @@ import type { RSVPItem } from "../../lib/types";
import { createProtectedAPIRoute } from "../../utils/auth-middleware"; import { createProtectedAPIRoute } from "../../utils/auth-middleware";
const objectsToCSV = (data: RSVPItem[]): string => { const objectsToCSV = (data: RSVPItem[]): string => {
const headers = ["name", "dietaryRestrictions", "notes", "timestamp"]; const headers = ["name", "attending", "dietaryRestrictions", "notes", "timestamp"];
const csvRows = [headers.join(",")]; const csvRows = [headers.join(",")];
data.forEach((entry) => { data.forEach((entry) => {
const row = headers.map((header) => { const row = headers.map((header) => {
const field = String(entry[header as keyof RSVPItem]); let field = entry[header as keyof RSVPItem];
const escaped = field.replace(/"/g, '""'); // Convert boolean to string for CSV
if (typeof field === 'boolean') {
field = field.toString();
}
// Handle null/undefined
if (field === null || field === undefined) {
field = '';
}
const escaped = String(field).replace(/"/g, '""');
return `"${escaped}"`; return `"${escaped}"`;
}); });
csvRows.push(row.join(",")); csvRows.push(row.join(","));
@ -22,18 +30,14 @@ const objectsToCSV = (data: RSVPItem[]): string => {
const csvToObjects = (csv: string): RSVPItem[] => { const csvToObjects = (csv: string): RSVPItem[] => {
const lines = csv.split("\n"); const lines = csv.split("\n");
const headers = lines[0].split(",").map((h) => h.trim()); const headers = lines[0].split(",").map((h) => h.trim());
const hasAttendingColumn = headers.includes("attending");
return lines return lines
.slice(1) .slice(1)
.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] || "";
@ -41,6 +45,13 @@ const csvToObjects = (csv: string): RSVPItem[] => {
entry[header as keyof RSVPItem] = value; entry[header as keyof RSVPItem] = value;
}); });
// If the attending column doesn't exist in the CSV, assume they're attending if they provided dietary restrictions
if (!hasAttendingColumn) {
entry.attending = Boolean(entry.dietaryRestrictions);
} else if (typeof entry.attending === 'string') {
entry.attending = entry.attending.toLowerCase() === 'true';
}
return entry as RSVPItem; return entry as RSVPItem;
}); });
}; };
@ -84,38 +95,29 @@ const handleGet: APIRoute = async ({ request }) => {
// POST: Submit a new RSVP (requires guest role) // POST: Submit a new RSVP (requires guest role)
const handlePost: APIRoute = async ({ request }) => { const handlePost: APIRoute = async ({ request }) => {
console.log("API endpoint hit - starting request processing");
const headers = { const headers = {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json", "Content-Type": "application/json",
}; };
try { try {
const FILE_KEY = "rsvp.csv"; const FILE_KEY = "rsvp.csv";
console.log("Parsing request body");
const newRsvp = await request.json(); const newRsvp = await request.json();
console.log("Received RSVP data:", newRsvp);
let existingRsvps: RSVPItem[] = []; let existingRsvps: RSVPItem[] = [];
console.log("Attempting to fetch existing RSVPs");
const fileContent = await getS3Data<string>(FILE_KEY); const fileContent = await getS3Data<string>(FILE_KEY);
if (fileContent) { if (fileContent) {
existingRsvps = csvToObjects(fileContent); existingRsvps = csvToObjects(fileContent);
console.log("Existing RSVPs loaded:", existingRsvps.length);
} }
existingRsvps.push({ existingRsvps.push({
...newRsvp, ...newRsvp,
attending: Boolean(newRsvp.attending),
notes: newRsvp.notes || "", notes: newRsvp.notes || "",
timestamp: new Date().toISOString(), dietaryRestrictions: newRsvp.dietaryRestrictions || "",
timestamp: newRsvp.timestamp || new Date().toISOString(),
}); });
console.log("Attempting to save updated RSVP list");
const csvContent = objectsToCSV(existingRsvps); const csvContent = objectsToCSV(existingRsvps);
await putS3Data(FILE_KEY, csvContent, "text/csv"); await putS3Data(FILE_KEY, csvContent, "text/csv");
return new Response(JSON.stringify({ success: true }), { return new Response(JSON.stringify({ success: true }), {
@ -128,7 +130,85 @@ const handlePost: APIRoute = async ({ request }) => {
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error instanceof Error ? error.message : "Unknown error", error: error instanceof Error ? error.message : "Unknown error",
details: error instanceof Error ? error.stack : undefined, }),
{
status: 500,
headers,
}
);
}
};
// DELETE: Delete an RSVP (requires admin role)
const handleDelete: APIRoute = async ({ request }) => {
const headers = {
"Content-Type": "application/json",
};
try {
const FILE_KEY = "rsvp.csv";
const { name, timestamp } = await request.json();
if (!name || !timestamp) {
return new Response(
JSON.stringify({
success: false,
error: "Name and timestamp are required",
}),
{
status: 400,
headers,
}
);
}
const fileContent = await getS3Data<string>(FILE_KEY);
if (!fileContent) {
return new Response(
JSON.stringify({
success: false,
error: "No RSVPs found",
}),
{
status: 404,
headers,
}
);
}
const rsvpList = csvToObjects(fileContent);
// Find exact match by name and timestamp
const updatedList = rsvpList.filter(
(rsvp) => !(rsvp.name === name && rsvp.timestamp === timestamp)
);
if (updatedList.length === rsvpList.length) {
return new Response(
JSON.stringify({
success: false,
error: "RSVP not found",
}),
{
status: 404,
headers,
}
);
}
const csvContent = objectsToCSV(updatedList);
await putS3Data(FILE_KEY, csvContent, "text/csv");
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers,
});
} catch (error) {
console.error("Error deleting RSVP:", error);
return new Response(
JSON.stringify({
success: false,
error: error instanceof Error ? error.message : "Unknown error",
}), }),
{ {
status: 500, status: 500,
@ -141,3 +221,4 @@ const handlePost: APIRoute = async ({ request }) => {
// Export protected routes // Export protected routes
export const GET = createProtectedAPIRoute(handleGet, "admin"); export const GET = createProtectedAPIRoute(handleGet, "admin");
export const POST = createProtectedAPIRoute(handlePost, "guest"); export const POST = createProtectedAPIRoute(handlePost, "guest");
export const DELETE = createProtectedAPIRoute(handleDelete, "admin");

View File

@ -13,7 +13,7 @@ import Layout from "../layouts/Layout.astro";
<div class="collapse collapse-plus bg-base-200"> <div class="collapse collapse-plus bg-base-200">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
Dress Code? Dress Code
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<p class="text-lg"> <p class="text-lg">
@ -27,7 +27,7 @@ import Layout from "../layouts/Layout.astro";
<div class="collapse collapse-plus bg-base-200"> <div class="collapse collapse-plus bg-base-200">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
Dietary Restrictions? Dietary Restrictions
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<p class="text-lg"> <p class="text-lg">
@ -41,7 +41,7 @@ import Layout from "../layouts/Layout.astro";
<div class="collapse collapse-plus bg-base-200"> <div class="collapse collapse-plus bg-base-200">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
Alcohol? Alcohol
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<p class="text-lg"> <p class="text-lg">
@ -54,7 +54,7 @@ import Layout from "../layouts/Layout.astro";
<div class="collapse collapse-plus bg-base-200"> <div class="collapse collapse-plus bg-base-200">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
Childcare? Childcare
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<p class="text-lg"> <p class="text-lg">
@ -68,7 +68,7 @@ import Layout from "../layouts/Layout.astro";
<div class="collapse collapse-plus bg-base-200"> <div class="collapse collapse-plus bg-base-200">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
Gifts? Gifts
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<p class="text-lg"> <p class="text-lg">
@ -99,7 +99,7 @@ import Layout from "../layouts/Layout.astro";
<div class="collapse collapse-plus bg-base-200"> <div class="collapse collapse-plus bg-base-200">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
Where Do I Contact You With All of My Concerns? I have all these concerns, what do I do?
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<div class="text-lg space-y-2"> <div class="text-lg space-y-2">

View File

@ -10,10 +10,10 @@ import SignIn from "../components/SignIn.tsx";
<div id="content-container" class="hidden"> <div id="content-container" class="hidden">
<div class="flex flex-col gap-8 max-w-5xl mx-auto p-6"> <div class="flex flex-col gap-8 max-w-5xl mx-auto p-6">
<img src="/hero.jpeg" alt="" height="250" width="250" class="rounded-full mx-auto"> <img src="/logo.jpg" alt="" height="250" width="250" class="rounded-full mx-auto">
<!-- Header --> <!-- Header -->
<div class="text-center space-y-4"> <div class="text-center space-y-4">
<div class="text-5xl font-normal" style="font-family: 'Great Vibes', cursive;">❤️ Natasha + Ixabat ❤️</div> <div class="text-5xl font-normal" style="font-family: 'Great Vibes', cursive;"><span class="text-2xl">❤️</span> Natasha + Ixabat <span class="text-3xl">❤️</span></div>
<p class="text-xl text-gray-600">We hope you can join us in celebration on</p> <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> <p class="text-2xl font-semibold">Saturday, June 7, 2025</p>
</div> </div>
@ -21,7 +21,7 @@ import SignIn from "../components/SignIn.tsx";
<!-- Event Details Cards --> <!-- Event Details Cards -->
<div class="flex flex-col md:flex-row gap-6"> <div class="flex flex-col md:flex-row gap-6">
<!-- Ceremony Card --> <!-- Ceremony Card -->
<div class="card bg-base-100 shadow-xl flex-1"> <div class="card bg-base-100 shadow-xl flex-1 border-2 border-primary">
<div class="card-body"> <div class="card-body">
<h2 class="card-title text-2xl justify-center">Ceremony</h2> <h2 class="card-title text-2xl justify-center">Ceremony</h2>
<div class="text-center space-y-2"> <div class="text-center space-y-2">
@ -40,7 +40,7 @@ import SignIn from "../components/SignIn.tsx";
</div> </div>
<!-- Reception Card --> <!-- Reception Card -->
<div class="card bg-base-100 shadow-xl flex-1"> <div class="card bg-base-100 shadow-xl flex-1 border-2 border-primary">
<div class="card-body"> <div class="card-body">
<h2 class="card-title text-2xl justify-center">Reception</h2> <h2 class="card-title text-2xl justify-center">Reception</h2>
<div class="text-center space-y-2"> <div class="text-center space-y-2">