Refactor event creation page to use reusable EventForm component
Replaced inlined event creation logic on `CreateEventPage` with a new reusable `EventForm` component. This decouples the form functionality, improves modularity, and enables future reuse for editing events. Enhanced validation and added support for event themes and additional settings within `EventForm`.
This commit is contained in:
@@ -1,218 +1,26 @@
|
||||
// src/app/dashboard/events/new/page.tsx
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React from "react";
|
||||
import { useEvents } from "@/context/event-context";
|
||||
import { EventCreate } from "@/client";
|
||||
import Navbar from "@/components/layout/navbar";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { RESERVED_SLUGS } from "@/lib/constants";
|
||||
import { EventForm } from "@/components/events/event-form";
|
||||
|
||||
export default function CreateEventPage() {
|
||||
const router = useRouter();
|
||||
const { createEvent, isCreating } = useEvents();
|
||||
const [slugError, setSlugError] = useState<string | null>(null);
|
||||
|
||||
const [formData, setFormData] = useState<EventCreate>({
|
||||
title: "",
|
||||
description: "",
|
||||
location_name: "",
|
||||
location_address: "",
|
||||
location_url: "",
|
||||
event_date: "",
|
||||
event_start_time: "",
|
||||
event_end_time: "",
|
||||
timezone: "UTC",
|
||||
slug: "",
|
||||
is_public: false,
|
||||
rsvp_enabled: true,
|
||||
});
|
||||
|
||||
const onChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { name, value, type } = e.target;
|
||||
|
||||
const checked =
|
||||
type === "checkbox" ? (e.target as HTMLInputElement).checked : undefined;
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
}));
|
||||
|
||||
// live validation for slug
|
||||
if (name === "slug") {
|
||||
if (RESERVED_SLUGS.includes(value)) {
|
||||
setSlugError(`The slug "${value}" is reserved.`);
|
||||
} else {
|
||||
setSlugError(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onTogglePublic = (checked: boolean) => {
|
||||
setFormData((prev) => ({ ...prev, is_public: checked }));
|
||||
};
|
||||
|
||||
const onSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (RESERVED_SLUGS.includes(formData.slug)) {
|
||||
alert(
|
||||
`The slug "${formData.slug}" is reserved and cannot be used. Please choose another slug.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const event = await createEvent(formData);
|
||||
router.push(`/dashboard/events/${event.slug}`);
|
||||
} catch (error) {
|
||||
console.error("Failed to create event:", error);
|
||||
alert("Failed to create event. Please check your data and try again.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Create New Event</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={onSubmit} className="grid grid-cols-1 gap-6">
|
||||
<div>
|
||||
<Label className={"mb-2"}>Event Title *</Label>
|
||||
<Input
|
||||
required
|
||||
name="title"
|
||||
placeholder="My Awesome Event"
|
||||
value={formData.title}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className={"mb-2"}>Description</Label>
|
||||
<Textarea
|
||||
name="description"
|
||||
placeholder="Quick description of your event"
|
||||
value={formData.description || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className={"mb-2"}>Event Date *</Label>
|
||||
<Input
|
||||
type="date"
|
||||
required
|
||||
name="event_date"
|
||||
value={formData.event_date}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className={"mb-2"}>Slug *</Label>
|
||||
<Input
|
||||
required
|
||||
name="slug"
|
||||
pattern="^[a-z0-9-]+$"
|
||||
placeholder="my-awesome-event"
|
||||
value={formData.slug}
|
||||
onChange={onChange}
|
||||
className={slugError ? "border-red-500" : ""}
|
||||
/>
|
||||
{slugError && (
|
||||
<p className="text-sm text-red-500 mt-1">{slugError}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className={"mb-2"}>Start Time</Label>
|
||||
<Input
|
||||
type="time"
|
||||
name="event_start_time"
|
||||
value={formData.event_start_time || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className={"mb-2"}>End Time</Label>
|
||||
<Input
|
||||
type="time"
|
||||
name="event_end_time"
|
||||
value={formData.event_end_time || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className={"mb-2"}>Location Name</Label>
|
||||
<Input
|
||||
name="location_name"
|
||||
placeholder="Venue name"
|
||||
value={formData.location_name || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className={"mb-2"}>Location Address</Label>
|
||||
<Input
|
||||
name="location_address"
|
||||
placeholder="123 Main Street"
|
||||
value={formData.location_address || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className={"mb-2"}>Location URL</Label>
|
||||
<Input
|
||||
type="url"
|
||||
name="location_url"
|
||||
placeholder="https://maps.app/location"
|
||||
value={formData.location_url || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="is_public"
|
||||
checked={formData.is_public}
|
||||
onCheckedChange={onTogglePublic}
|
||||
/>
|
||||
<Label htmlFor="is_public">Public Event</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={isCreating} type="submit">
|
||||
{isCreating ? "Creating..." : "Create Event"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
<Card className="shadow-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Create New Event</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<EventForm
|
||||
mode="create"
|
||||
onSubmit={createEvent}
|
||||
isSubmitting={isCreating}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
591
frontend/src/components/events/event-form.tsx
Normal file
591
frontend/src/components/events/event-form.tsx
Normal file
@@ -0,0 +1,591 @@
|
||||
// src/components/events/event-form.tsx
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { EventCreate, EventResponse, EventThemeResponse } from "@/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { RESERVED_SLUGS } from "@/lib/constants";
|
||||
import { useEventThemes } from "@/context/event-theme-context";
|
||||
|
||||
// Define timezones - this should be expanded with a complete list
|
||||
const TIMEZONES = [
|
||||
"UTC",
|
||||
"America/New_York",
|
||||
"America/Chicago",
|
||||
"America/Denver",
|
||||
"America/Los_Angeles",
|
||||
"Europe/London",
|
||||
"Europe/Paris",
|
||||
"Asia/Tokyo",
|
||||
"Australia/Sydney",
|
||||
];
|
||||
|
||||
type EventFormProps = {
|
||||
event?: EventResponse;
|
||||
// themes?: EventThemeResponse[];
|
||||
mode: "create" | "edit";
|
||||
onSubmit: (data: EventCreate) => Promise<EventResponse>;
|
||||
isSubmitting?: boolean;
|
||||
};
|
||||
|
||||
export function EventForm({
|
||||
event,
|
||||
// themes = [],
|
||||
mode,
|
||||
onSubmit,
|
||||
isSubmitting = false,
|
||||
}: EventFormProps) {
|
||||
const router = useRouter();
|
||||
const [slugError, setSlugError] = useState<string | null>(null);
|
||||
const [formErrors, setFormErrors] = useState<Record<string, string>>({});
|
||||
const { themes } = useEventThemes();
|
||||
const [formData, setFormData] = useState<EventCreate>({
|
||||
title: event?.title || "",
|
||||
description: event?.description || "",
|
||||
location_name: event?.location_name || "",
|
||||
location_address: event?.location_address || "",
|
||||
location_url: event?.location_url || "",
|
||||
event_date: event?.event_date || "",
|
||||
event_start_time: event?.event_start_time || "",
|
||||
event_end_time: event?.event_end_time || "",
|
||||
timezone: event?.timezone || "UTC",
|
||||
rsvp_deadline: event?.rsvp_deadline || "",
|
||||
is_public: event?.is_public !== undefined ? event.is_public : false,
|
||||
access_code: event?.access_code || "",
|
||||
theme_id: event?.theme_id || null,
|
||||
custom_theme_settings: event?.custom_theme_settings || null,
|
||||
additional_info: event?.additional_info || null,
|
||||
is_active: event?.is_active !== undefined ? event.is_active : true,
|
||||
rsvp_enabled: event?.rsvp_enabled !== undefined ? event.rsvp_enabled : true,
|
||||
gift_registry_enabled:
|
||||
event?.gift_registry_enabled !== undefined
|
||||
? event.gift_registry_enabled
|
||||
: false,
|
||||
updates_enabled:
|
||||
event?.updates_enabled !== undefined ? event.updates_enabled : false,
|
||||
max_guests_per_invitation: event?.max_guests_per_invitation || null,
|
||||
contact_email: event?.contact_email || "",
|
||||
contact_phone: event?.contact_phone || "",
|
||||
slug: event?.slug || "",
|
||||
});
|
||||
|
||||
const onChange = (
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||
) => {
|
||||
const { name, value, type } = e.target;
|
||||
|
||||
const checked =
|
||||
type === "checkbox" ? (e.target as HTMLInputElement).checked : undefined;
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
}));
|
||||
|
||||
// live validation for slug
|
||||
if (name === "slug" && mode === "create") {
|
||||
if (RESERVED_SLUGS.includes(value)) {
|
||||
setSlugError(`The slug "${value}" is reserved.`);
|
||||
} else {
|
||||
setSlugError(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear error for field when changed
|
||||
if (formErrors[name]) {
|
||||
setFormErrors((prev) => {
|
||||
const updated = { ...prev };
|
||||
delete updated[name];
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectChange = (name: string, value: string | null) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const onToggleSwitch = (name: string, checked: boolean) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: checked,
|
||||
}));
|
||||
};
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
if (!formData.title.trim()) {
|
||||
errors.title = "Event title is required";
|
||||
}
|
||||
|
||||
if (!formData.event_date) {
|
||||
errors.event_date = "Event date is required";
|
||||
}
|
||||
|
||||
if (!formData.slug.trim()) {
|
||||
errors.slug = "Event slug is required";
|
||||
} else if (mode === "create" && RESERVED_SLUGS.includes(formData.slug)) {
|
||||
errors.slug = `The slug "${formData.slug}" is reserved`;
|
||||
}
|
||||
|
||||
if (formData.is_public === false && !formData.access_code) {
|
||||
errors.access_code = "Access code is required for private events";
|
||||
}
|
||||
|
||||
if (formData.contact_email && !validateEmail(formData.contact_email)) {
|
||||
errors.contact_email = "Invalid email address";
|
||||
}
|
||||
|
||||
setFormErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
const validateEmail = (email: string): boolean => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await onSubmit(formData);
|
||||
router.push(`/dashboard/events/${result.slug}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to ${mode} event:`, error);
|
||||
alert(`Failed to ${mode} event. Please check your data and try again.`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="grid grid-cols-1 gap-6">
|
||||
{/* Basic Event Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Basic Information</h3>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="title" className="mb-2">
|
||||
Event Title *
|
||||
</Label>
|
||||
<Input
|
||||
id="title"
|
||||
required
|
||||
name="title"
|
||||
placeholder="My Awesome Event"
|
||||
value={formData.title}
|
||||
onChange={onChange}
|
||||
className={formErrors.title ? "border-red-500" : ""}
|
||||
/>
|
||||
{formErrors.title && (
|
||||
<p className="text-red-500 text-sm mt-1">{formErrors.title}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="description" className="mb-2">
|
||||
Description
|
||||
</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
placeholder="Quick description of your event"
|
||||
value={formData.description || ""}
|
||||
onChange={onChange}
|
||||
rows={4}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="theme_id" className="mb-2">
|
||||
Event Theme
|
||||
</Label>
|
||||
<Select
|
||||
name="theme_id"
|
||||
value={formData.theme_id || ""}
|
||||
onValueChange={(value) =>
|
||||
onSelectChange("theme_id", value === "none" ? null : value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="theme_id">
|
||||
<SelectValue placeholder="Select a theme" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">None</SelectItem>
|
||||
{themes &&
|
||||
themes.map((theme) => (
|
||||
<SelectItem key={theme.id} value={theme.id}>
|
||||
{theme.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Choose a visual theme for your event or select 'None' for a default
|
||||
appearance
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date & Time Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Date & Time</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="event_date" className="mb-2">
|
||||
Event Date *
|
||||
</Label>
|
||||
<Input
|
||||
id="event_date"
|
||||
type="date"
|
||||
required
|
||||
name="event_date"
|
||||
value={formData.event_date}
|
||||
onChange={onChange}
|
||||
className={formErrors.event_date ? "border-red-500" : ""}
|
||||
/>
|
||||
{formErrors.event_date && (
|
||||
<p className="text-red-500 text-sm mt-1">
|
||||
{formErrors.event_date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="event_start_time" className="mb-2">
|
||||
Start Time
|
||||
</Label>
|
||||
<Input
|
||||
id="event_start_time"
|
||||
type="time"
|
||||
name="event_start_time"
|
||||
value={formData.event_start_time || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="event_end_time" className="mb-2">
|
||||
End Time
|
||||
</Label>
|
||||
<Input
|
||||
id="event_end_time"
|
||||
type="time"
|
||||
name="event_end_time"
|
||||
value={formData.event_end_time || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="timezone" className="mb-2">
|
||||
Timezone *
|
||||
</Label>
|
||||
<Select
|
||||
name="timezone"
|
||||
value={formData.timezone}
|
||||
onValueChange={(value) => onSelectChange("timezone", value)}
|
||||
>
|
||||
<SelectTrigger id="timezone">
|
||||
<SelectValue placeholder="Select timezone" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TIMEZONES.map((timezone) => (
|
||||
<SelectItem key={timezone} value={timezone}>
|
||||
{timezone}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="rsvp_deadline" className="mb-2">
|
||||
RSVP Deadline
|
||||
</Label>
|
||||
<Input
|
||||
id="rsvp_deadline"
|
||||
type="date"
|
||||
name="rsvp_deadline"
|
||||
value={formData.rsvp_deadline || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Location Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Location</h3>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="location_name" className="mb-2">
|
||||
Location Name
|
||||
</Label>
|
||||
<Input
|
||||
id="location_name"
|
||||
name="location_name"
|
||||
placeholder="Venue Name"
|
||||
value={formData.location_name || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="location_address" className="mb-2">
|
||||
Address
|
||||
</Label>
|
||||
<Textarea
|
||||
id="location_address"
|
||||
name="location_address"
|
||||
placeholder="Full address"
|
||||
value={formData.location_address || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="location_url" className="mb-2">
|
||||
Location URL
|
||||
</Label>
|
||||
<Input
|
||||
id="location_url"
|
||||
name="location_url"
|
||||
type="url"
|
||||
placeholder="https://maps.example.com/venue"
|
||||
value={formData.location_url || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Optional: Add a map link or venue website
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contact Information */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Contact Information</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="contact_email" className="mb-2">
|
||||
Contact Email
|
||||
</Label>
|
||||
<Input
|
||||
id="contact_email"
|
||||
name="contact_email"
|
||||
type="email"
|
||||
placeholder="event@example.com"
|
||||
value={formData.contact_email || ""}
|
||||
onChange={onChange}
|
||||
className={formErrors.contact_email ? "border-red-500" : ""}
|
||||
/>
|
||||
{formErrors.contact_email && (
|
||||
<p className="text-red-500 text-sm mt-1">
|
||||
{formErrors.contact_email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="contact_phone" className="mb-2">
|
||||
Contact Phone
|
||||
</Label>
|
||||
<Input
|
||||
id="contact_phone"
|
||||
name="contact_phone"
|
||||
type="tel"
|
||||
placeholder="+1 (555) 123-4567"
|
||||
value={formData.contact_phone || ""}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Event Settings */}
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">Event Settings</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label htmlFor="slug" className="mb-2">
|
||||
Event Slug *
|
||||
</Label>
|
||||
<Input
|
||||
id="slug"
|
||||
required
|
||||
name="slug"
|
||||
placeholder="my-event"
|
||||
value={formData.slug}
|
||||
onChange={onChange}
|
||||
disabled={mode === "edit"}
|
||||
className={formErrors.slug ? "border-red-500" : ""}
|
||||
/>
|
||||
{formErrors.slug && (
|
||||
<p className="text-red-500 text-sm mt-1">{formErrors.slug}</p>
|
||||
)}
|
||||
{mode === "create" && (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
This will be used as the URL for your event
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="max_guests_per_invitation" className="mb-2">
|
||||
Max Guests Per RSVP
|
||||
</Label>
|
||||
<Input
|
||||
id="max_guests_per_invitation"
|
||||
name="max_guests_per_invitation"
|
||||
type="number"
|
||||
min="1"
|
||||
placeholder="1"
|
||||
value={
|
||||
formData.max_guests_per_invitation === null
|
||||
? ""
|
||||
: formData.max_guests_per_invitation
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
? parseInt(e.target.value, 10)
|
||||
: null;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
max_guests_per_invitation: value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Leave empty for unlimited guests per invitation
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Switch
|
||||
id="is_public"
|
||||
checked={formData.is_public}
|
||||
onCheckedChange={(checked) =>
|
||||
onToggleSwitch("is_public", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="is_public">Make event public</Label>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground ml-10">
|
||||
Public events can be viewed by anyone with the link
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!formData.is_public && (
|
||||
<div>
|
||||
<Label htmlFor="access_code" className="mb-2">
|
||||
Access Code *
|
||||
</Label>
|
||||
<Input
|
||||
id="access_code"
|
||||
name="access_code"
|
||||
placeholder="secret-code"
|
||||
value={formData.access_code || ""}
|
||||
onChange={onChange}
|
||||
className={formErrors.access_code ? "border-red-500" : ""}
|
||||
required={!formData.is_public}
|
||||
/>
|
||||
{formErrors.access_code && (
|
||||
<p className="text-red-500 text-sm mt-1">
|
||||
{formErrors.access_code}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Required for private events - guests will need this code to access
|
||||
the event
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id="rsvp_enabled"
|
||||
checked={formData.rsvp_enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
onToggleSwitch("rsvp_enabled", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="rsvp_enabled">Enable RSVP functionality</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id="gift_registry_enabled"
|
||||
checked={formData.gift_registry_enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
onToggleSwitch("gift_registry_enabled", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="gift_registry_enabled">Enable Gift Registry</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id="updates_enabled"
|
||||
checked={formData.updates_enabled}
|
||||
onCheckedChange={(checked) =>
|
||||
onToggleSwitch("updates_enabled", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="updates_enabled">Enable Event Updates</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
id="is_active"
|
||||
checked={formData.is_active}
|
||||
onCheckedChange={(checked) =>
|
||||
onToggleSwitch("is_active", checked)
|
||||
}
|
||||
/>
|
||||
<Label htmlFor="is_active">Event Active</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form Actions */}
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<Button type="button" variant="outline" onClick={() => router.back()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
{isSubmitting
|
||||
? "Saving..."
|
||||
: mode === "create"
|
||||
? "Create Event"
|
||||
: "Update Event"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user