S3
This commit is contained in:
@ -1,96 +1,189 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../styles/global.css';
|
||||
import React, { useState } from "react";
|
||||
import "../styles/global.css";
|
||||
|
||||
interface FormData {
|
||||
name: string;
|
||||
dietaryRestrictions: string;
|
||||
name: string;
|
||||
dietaryRestrictions: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const RSVPForm = () => {
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
name: '',
|
||||
dietaryRestrictions: '',
|
||||
});
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
name: "",
|
||||
dietaryRestrictions: "",
|
||||
});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
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;
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!isFormValid) return;
|
||||
|
||||
// Here you would typically send the data to a server
|
||||
console.log(formData);
|
||||
setIsSubmitting(true);
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
|
||||
// Reset form
|
||||
setFormData({
|
||||
name: '',
|
||||
dietaryRestrictions: '',
|
||||
});
|
||||
try {
|
||||
// Create an AbortController for timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
||||
|
||||
// You might want to replace this with a proper toast notification
|
||||
alert('Thank you for your RSVP!');
|
||||
};
|
||||
const response = await fetch("/api/rsvp", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
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>
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
<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
|
||||
/>
|
||||
</div>
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error("Server response:", errorText);
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
{/* 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"
|
||||
/>
|
||||
</div>
|
||||
const data: ApiResponse = await response.json();
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary w-full"
|
||||
disabled={!isFormValid}
|
||||
>
|
||||
I'll Be There!
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || "Failed to submit RSVP");
|
||||
}
|
||||
|
||||
setFormData({
|
||||
name: "",
|
||||
dietaryRestrictions: "",
|
||||
});
|
||||
setSuccess(true);
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') {
|
||||
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;
|
||||
|
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;
|
Reference in New Issue
Block a user