S3
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
S3_REGION=outer-space
|
||||||
|
S3_HOST=s3.cool.site
|
||||||
|
S3_ACCESS_KEY=your_access_key_here
|
||||||
|
S3_SECRET_KEY=your_secret_key_here
|
||||||
|
S3_BUCKET_NAME=your_bucket_name
|
||||||
|
SECRET_CODE=super_secret_code
|
@ -11,7 +11,9 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.0.2",
|
"@astrojs/node": "^9.0.2",
|
||||||
"@astrojs/react": "^4.2.0",
|
"@astrojs/react": "^4.2.0",
|
||||||
|
"@aws-sdk/client-s3": "^3.740.0",
|
||||||
"@tailwindcss/vite": "^4.0.3",
|
"@tailwindcss/vite": "^4.0.3",
|
||||||
|
"@types/node": "^22.13.0",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"astro": "^5.2.3",
|
"astro": "^5.2.3",
|
||||||
|
1242
pnpm-lock.yaml
generated
1242
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,96 +1,189 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import '../styles/global.css';
|
import "../styles/global.css";
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
dietaryRestrictions: string;
|
dietaryRestrictions: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiResponse {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RSVPForm = () => {
|
const RSVPForm = () => {
|
||||||
const [formData, setFormData] = useState<FormData>({
|
const [formData, setFormData] = useState<FormData>({
|
||||||
name: '',
|
name: "",
|
||||||
dietaryRestrictions: '',
|
dietaryRestrictions: "",
|
||||||
});
|
});
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
const isFormValid =
|
const isFormValid = formData.name.trim().length > 0;
|
||||||
formData.name.trim().length > 0;
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!isFormValid) return;
|
if (!isFormValid) return;
|
||||||
|
|
||||||
// Here you would typically send the data to a server
|
setIsSubmitting(true);
|
||||||
console.log(formData);
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
// Reset form
|
try {
|
||||||
setFormData({
|
// Create an AbortController for timeout
|
||||||
name: '',
|
const controller = new AbortController();
|
||||||
dietaryRestrictions: '',
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||||
});
|
|
||||||
|
|
||||||
// You might want to replace this with a proper toast notification
|
const response = await fetch("/api/rsvp", {
|
||||||
alert('Thank you for your RSVP!');
|
method: "POST",
|
||||||
};
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
clearTimeout(timeoutId);
|
||||||
<div className="max-w-2xl mx-auto p-6">
|
|
||||||
<div className="card bg-base-100 shadow-xl">
|
|
||||||
<div className="card-body flex flex-col gap-6">
|
|
||||||
<h2 className="card-title text-2xl font-bold text-center justify-center">
|
|
||||||
Wedding RSVP
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
if (!response.ok) {
|
||||||
{/* Name Input */}
|
const errorText = await response.text();
|
||||||
<div className="flex flex-col gap-2">
|
console.error("Server response:", errorText);
|
||||||
<label htmlFor="name" className="label">
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
<span className="label-text">Full Name *</span>
|
}
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={(e) =>
|
|
||||||
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
||||||
}
|
|
||||||
placeholder="Enter your full name"
|
|
||||||
className="input input-bordered w-full"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dietary Restrictions Input */}
|
const data: ApiResponse = await response.json();
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Submit Button */}
|
if (!data.success) {
|
||||||
<button
|
throw new Error(data.error || "Failed to submit RSVP");
|
||||||
type="submit"
|
}
|
||||||
className="btn btn-primary w-full"
|
|
||||||
disabled={!isFormValid}
|
setFormData({
|
||||||
>
|
name: "",
|
||||||
I'll Be There!
|
dietaryRestrictions: "",
|
||||||
</button>
|
});
|
||||||
</form>
|
setSuccess(true);
|
||||||
</div>
|
} catch (err) {
|
||||||
</div>
|
if (err.name === 'AbortError') {
|
||||||
</div>
|
setError('Request timed out. Please try again.');
|
||||||
);
|
} else {
|
||||||
|
setError(
|
||||||
|
err instanceof Error ? err.message : "Failed to submit RSVP. Please try again."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.error("Error submitting RSVP:", err);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto p-6">
|
||||||
|
<div className="card bg-base-100 shadow-xl">
|
||||||
|
<div className="card-body flex flex-col gap-6">
|
||||||
|
<h2 className="card-title text-2xl font-bold text-center justify-center">
|
||||||
|
Wedding RSVP
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="alert alert-error">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="stroke-current shrink-0 h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{success && (
|
||||||
|
<div className="alert alert-success">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="stroke-current shrink-0 h-6 w-6"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>Thank you for your RSVP!</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
||||||
|
{/* Name Input */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label htmlFor="name" className="label">
|
||||||
|
<span className="label-text">Full Name *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
||||||
|
}
|
||||||
|
placeholder="Enter your full name"
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
required
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary w-full"
|
||||||
|
disabled={!isFormValid || isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<span className="loading loading-spinner"></span>
|
||||||
|
Submitting...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
"I'll Be There!"
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RSVPForm;
|
export default RSVPForm;
|
||||||
|
91
src/components/SignIn.tsx
Normal file
91
src/components/SignIn.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
interface SignInProps {
|
||||||
|
onSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignIn = ({ onSuccess }: SignInProps) => {
|
||||||
|
const [code, setCode] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/auth", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ code }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || !data.success) {
|
||||||
|
throw new Error(data.error || "Invalid code");
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.setItem("isAuthenticated", "true");
|
||||||
|
|
||||||
|
// Dispatch custom event instead of calling callback
|
||||||
|
const event = new CustomEvent('auth-success', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Still call onSuccess if provided (for flexibility)
|
||||||
|
onSuccess?.();
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Authentication failed");
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto p-6">
|
||||||
|
<div className="card bg-base-100 shadow-xl">
|
||||||
|
<div className="card-body flex flex-col gap-6">
|
||||||
|
<h2 className="card-title text-2xl font-bold text-center justify-center">
|
||||||
|
Enter Secret Code
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="alert alert-error">
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
placeholder="Enter secret code"
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
required
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary w-full"
|
||||||
|
disabled={!code || isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? "Verifying..." : "Continue"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignIn;
|
41
src/pages/api/auth.ts
Normal file
41
src/pages/api/auth.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { code } = await request.json();
|
||||||
|
const secretCode = process.env.SECRET_CODE || import.meta.env.SECRET_CODE;
|
||||||
|
|
||||||
|
if (!code || code !== secretCode) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: "Invalid code",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 401,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ success: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: "Authentication failed",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
148
src/pages/api/rsvp.ts
Normal file
148
src/pages/api/rsvp.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
import type { APIRoute } from "astro";
|
||||||
|
|
||||||
|
// Define the structure of our RSVP data
|
||||||
|
interface RSVPEntry {
|
||||||
|
name: string;
|
||||||
|
dietaryRestrictions: string;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert array of objects to CSV string
|
||||||
|
const objectsToCSV = (data: RSVPEntry[]): string => {
|
||||||
|
// Define headers
|
||||||
|
const headers = ["name", "dietaryRestrictions", "timestamp"];
|
||||||
|
const csvRows = [headers.join(",")];
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
data.forEach((entry) => {
|
||||||
|
const row = headers.map((header) => {
|
||||||
|
// Escape commas and quotes in the content
|
||||||
|
const field = String(entry[header as keyof RSVPEntry]);
|
||||||
|
const escaped = field.replace(/"/g, '""');
|
||||||
|
return `"${escaped}"`;
|
||||||
|
});
|
||||||
|
csvRows.push(row.join(","));
|
||||||
|
});
|
||||||
|
|
||||||
|
return csvRows.join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to parse CSV string to array of objects
|
||||||
|
const csvToObjects = (csv: string): RSVPEntry[] => {
|
||||||
|
const lines = csv.split("\n");
|
||||||
|
const headers = lines[0].split(",").map(h => h.trim());
|
||||||
|
|
||||||
|
return lines
|
||||||
|
.slice(1) // Skip headers
|
||||||
|
.filter(line => line.trim()) // Skip empty lines
|
||||||
|
.map(line => {
|
||||||
|
const values = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || [];
|
||||||
|
const entry: Partial<RSVPEntry> = {};
|
||||||
|
|
||||||
|
headers.forEach((header, index) => {
|
||||||
|
let value = values[index] || "";
|
||||||
|
// Remove quotes and unescape doubled quotes
|
||||||
|
value = value.replace(/^"(.*)"$/, "$1").replace(/""/g, '"');
|
||||||
|
entry[header as keyof RSVPEntry] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return entry as RSVPEntry;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
|
console.log("API endpoint hit - starting request processing");
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const s3Host = process.env.S3_HOST || import.meta.env.S3_HOST;
|
||||||
|
const endpoint = `https://${s3Host}`;
|
||||||
|
|
||||||
|
console.log("Creating S3 client with config:", {
|
||||||
|
region: process.env.S3_REGION || import.meta.env.S3_REGION,
|
||||||
|
endpoint,
|
||||||
|
});
|
||||||
|
|
||||||
|
const s3Client = new S3Client({
|
||||||
|
region: process.env.S3_REGION || import.meta.env.S3_REGION,
|
||||||
|
endpoint,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: process.env.S3_ACCESS_KEY || import.meta.env.S3_ACCESS_KEY,
|
||||||
|
secretAccessKey: process.env.S3_SECRET_KEY || import.meta.env.S3_SECRET_KEY,
|
||||||
|
},
|
||||||
|
forcePathStyle: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const BUCKET_NAME = process.env.S3_BUCKET_NAME || import.meta.env.S3_BUCKET_NAME;
|
||||||
|
const FILE_KEY = "rsvp.csv"; // Changed to CSV extension
|
||||||
|
|
||||||
|
console.log("Parsing request body");
|
||||||
|
const newRsvp = await request.json();
|
||||||
|
console.log("Received RSVP data:", newRsvp);
|
||||||
|
|
||||||
|
// Try to get existing RSVPs
|
||||||
|
let existingRsvps: RSVPEntry[] = [];
|
||||||
|
try {
|
||||||
|
console.log("Attempting to fetch existing RSVPs");
|
||||||
|
const getCommand = new GetObjectCommand({
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: FILE_KEY,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Sending GET request to S3");
|
||||||
|
const response = await s3Client.send(getCommand);
|
||||||
|
console.log("Received response from S3 GET");
|
||||||
|
|
||||||
|
const fileContent = await response.Body?.transformToString();
|
||||||
|
if (fileContent) {
|
||||||
|
existingRsvps = csvToObjects(fileContent);
|
||||||
|
console.log("Existing RSVPs loaded:", existingRsvps.length);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("No existing RSVP file found or error:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new RSVP
|
||||||
|
existingRsvps.push({
|
||||||
|
...newRsvp,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Attempting to save updated RSVP list");
|
||||||
|
const csvContent = objectsToCSV(existingRsvps);
|
||||||
|
|
||||||
|
const putCommand = new PutObjectCommand({
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: FILE_KEY,
|
||||||
|
Body: csvContent,
|
||||||
|
ContentType: "text/csv",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Sending PUT request to S3");
|
||||||
|
await s3Client.send(putCommand);
|
||||||
|
console.log("Successfully saved to S3");
|
||||||
|
|
||||||
|
return new Response(JSON.stringify({ success: true }), {
|
||||||
|
status: 200,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error handling RSVP:", error);
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
details: error instanceof Error ? error.stack : undefined,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import RSVP from "../components/RSVP.tsx";
|
import RSVP from "../components/RSVP.tsx";
|
||||||
|
import SignIn from "../components/SignIn.tsx";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
@ -9,10 +10,32 @@ import RSVP from "../components/RSVP.tsx";
|
|||||||
Please RSVP using the form below:
|
Please RSVP using the form below:
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RSVP client:load />
|
<div id="auth-container">
|
||||||
|
<SignIn client:load onSuccess={() => {}} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="rsvp-container" class="hidden">
|
||||||
|
<RSVP client:load />
|
||||||
|
</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="/">Home</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Check auth state on page load
|
||||||
|
const isAuthenticated =
|
||||||
|
sessionStorage.getItem("isAuthenticated") === "true";
|
||||||
|
if (isAuthenticated) {
|
||||||
|
document.getElementById("auth-container")?.classList.add("hidden");
|
||||||
|
document.getElementById("rsvp-container")?.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listener for custom event from SignIn component
|
||||||
|
document.addEventListener("auth-success", () => {
|
||||||
|
document.getElementById("auth-container")?.classList.add("hidden");
|
||||||
|
document.getElementById("rsvp-container")?.classList.remove("hidden");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { vitePreprocess } from '@astrojs/svelte';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
preprocess: vitePreprocess(),
|
|
||||||
}
|
|
Reference in New Issue
Block a user