855 lines
No EOL
32 KiB
TypeScript
855 lines
No EOL
32 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import toast from 'react-hot-toast';
|
|
import { Authentication } from '../../../scripts/pocketbase/Authentication';
|
|
import { Update } from '../../../scripts/pocketbase/Update';
|
|
import { FileManager } from '../../../scripts/pocketbase/FileManager';
|
|
import { DataSyncService } from '../../../scripts/database/DataSyncService';
|
|
import { Collections } from '../../../schemas/pocketbase/schema';
|
|
import { EventRequestStatus } from '../../../schemas/pocketbase';
|
|
|
|
// Form sections
|
|
import PRSection from './PRSection';
|
|
import EventDetailsSection from './EventDetailsSection';
|
|
import TAPFormSection from './TAPFormSection';
|
|
import ASFundingSection from './ASFundingSection';
|
|
import { EventRequestFormPreview } from './EventRequestFormPreview';
|
|
import type { InvoiceData, InvoiceItem } from './InvoiceBuilder';
|
|
|
|
// Animation variants
|
|
const containerVariants = {
|
|
hidden: { opacity: 0, y: 20 },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
transition: {
|
|
type: "spring",
|
|
stiffness: 300,
|
|
damping: 30,
|
|
staggerChildren: 0.1
|
|
}
|
|
}
|
|
};
|
|
|
|
const itemVariants = {
|
|
hidden: { opacity: 0, y: 20 },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
transition: {
|
|
type: "spring",
|
|
stiffness: 300,
|
|
damping: 24
|
|
}
|
|
}
|
|
};
|
|
|
|
// Form data interface - based on the schema EventRequest but with form-specific fields
|
|
export interface EventRequestFormData {
|
|
// Fields from EventRequest
|
|
name: string;
|
|
location: string;
|
|
start_date_time: string;
|
|
end_date_time: string;
|
|
event_description: string;
|
|
flyers_needed: boolean;
|
|
photography_needed: boolean;
|
|
as_funding_required: boolean;
|
|
food_drinks_being_served: boolean;
|
|
itemized_invoice?: string;
|
|
status?: string;
|
|
created_by?: string;
|
|
id?: string;
|
|
created?: string;
|
|
updated?: string;
|
|
|
|
// Additional form-specific fields
|
|
flyer_type: string[];
|
|
other_flyer_type: string;
|
|
flyer_advertising_start_date: string;
|
|
flyer_additional_requests: string;
|
|
required_logos: string[];
|
|
other_logos: File[]; // Form uses File objects, schema uses strings
|
|
advertising_format: string;
|
|
will_or_have_room_booking: boolean;
|
|
expected_attendance: number;
|
|
room_booking: File | null;
|
|
invoice: File | null;
|
|
invoice_files: File[];
|
|
invoiceData: InvoiceData;
|
|
needs_graphics?: boolean | null;
|
|
needs_as_funding?: boolean | null;
|
|
formReviewed?: boolean; // Track if the form has been reviewed
|
|
}
|
|
|
|
// Add CustomAlert import
|
|
import CustomAlert from '../universal/CustomAlert';
|
|
|
|
const EventRequestForm: React.FC = () => {
|
|
const [currentStep, setCurrentStep] = useState<number>(1);
|
|
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Initialize form data
|
|
const [formData, setFormData] = useState<EventRequestFormData>({
|
|
name: '',
|
|
location: '',
|
|
start_date_time: '',
|
|
end_date_time: '',
|
|
event_description: '',
|
|
flyers_needed: false,
|
|
flyer_type: [],
|
|
other_flyer_type: '',
|
|
flyer_advertising_start_date: '',
|
|
flyer_additional_requests: '',
|
|
photography_needed: false,
|
|
required_logos: [],
|
|
other_logos: [],
|
|
advertising_format: '',
|
|
will_or_have_room_booking: false,
|
|
expected_attendance: 0,
|
|
room_booking: null,
|
|
as_funding_required: false,
|
|
food_drinks_being_served: false,
|
|
itemized_invoice: '',
|
|
invoice: null,
|
|
invoice_files: [], // Initialize empty array for multiple invoice files
|
|
needs_graphics: null,
|
|
needs_as_funding: false,
|
|
invoiceData: {
|
|
items: [],
|
|
subtotal: 0,
|
|
taxAmount: 0,
|
|
tipAmount: 0,
|
|
total: 0,
|
|
vendor: ''
|
|
},
|
|
formReviewed: false // Initialize as false
|
|
});
|
|
|
|
// Save form data to localStorage
|
|
useEffect(() => {
|
|
const formDataToSave = { ...formData };
|
|
// Remove file objects before saving to localStorage
|
|
const dataToStore = {
|
|
...formDataToSave,
|
|
other_logos: [],
|
|
room_booking: null,
|
|
invoice: null,
|
|
invoice_files: []
|
|
};
|
|
|
|
localStorage.setItem('eventRequestFormData', JSON.stringify(dataToStore));
|
|
|
|
// Also update the preview data
|
|
window.dispatchEvent(new CustomEvent('formDataUpdated', {
|
|
detail: { formData: formDataToSave }
|
|
}));
|
|
}, [formData]);
|
|
|
|
// Load form data from localStorage on initial load
|
|
useEffect(() => {
|
|
const savedData = localStorage.getItem('eventRequestFormData');
|
|
if (savedData) {
|
|
try {
|
|
const parsedData = JSON.parse(savedData);
|
|
setFormData(prevData => ({
|
|
...prevData,
|
|
...parsedData
|
|
}));
|
|
} catch (e) {
|
|
console.error('Error parsing saved form data:', e);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
// Handle form section data changes
|
|
const handleSectionDataChange = (sectionData: Partial<EventRequestFormData>) => {
|
|
// Ensure both needs_graphics and flyers_needed are synchronized
|
|
if (sectionData.flyers_needed !== undefined && sectionData.needs_graphics === undefined) {
|
|
sectionData.needs_graphics = sectionData.flyers_needed ? true : false;
|
|
}
|
|
|
|
// Ensure both needs_as_funding and as_funding_required are synchronized
|
|
if (sectionData.needs_as_funding !== undefined && sectionData.as_funding_required === undefined) {
|
|
sectionData.as_funding_required = sectionData.needs_as_funding ? true : false;
|
|
}
|
|
|
|
setFormData(prevData => {
|
|
// Save to localStorage
|
|
const updatedData = { ...prevData, ...sectionData };
|
|
localStorage.setItem('eventRequestFormData', JSON.stringify(updatedData));
|
|
return updatedData;
|
|
});
|
|
};
|
|
|
|
// Add this function before the handleSubmit function
|
|
const resetForm = () => {
|
|
setFormData({
|
|
name: '',
|
|
location: '',
|
|
start_date_time: '',
|
|
end_date_time: '',
|
|
event_description: '',
|
|
flyers_needed: false,
|
|
flyer_type: [],
|
|
other_flyer_type: '',
|
|
flyer_advertising_start_date: '',
|
|
flyer_additional_requests: '',
|
|
photography_needed: false,
|
|
required_logos: [],
|
|
other_logos: [],
|
|
advertising_format: '',
|
|
will_or_have_room_booking: false,
|
|
expected_attendance: 0,
|
|
room_booking: null, // No room booking by default
|
|
as_funding_required: false,
|
|
food_drinks_being_served: false,
|
|
itemized_invoice: '',
|
|
invoice: null,
|
|
invoice_files: [], // Reset multiple invoice files
|
|
needs_graphics: null,
|
|
needs_as_funding: false,
|
|
invoiceData: {
|
|
items: [],
|
|
subtotal: 0,
|
|
taxAmount: 0,
|
|
tipAmount: 0,
|
|
total: 0,
|
|
vendor: ''
|
|
},
|
|
formReviewed: false // Reset review status
|
|
});
|
|
|
|
// Reset to first step
|
|
setCurrentStep(1);
|
|
};
|
|
|
|
// Handle form submission
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// Check if the form has been reviewed
|
|
if (!formData.formReviewed) {
|
|
toast.error('Please review your form before submitting');
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const auth = Authentication.getInstance();
|
|
const update = Update.getInstance();
|
|
const fileManager = FileManager.getInstance();
|
|
const dataSync = DataSyncService.getInstance();
|
|
|
|
if (!auth.isAuthenticated()) {
|
|
// Don't show error toast on dashboard page for unauthenticated users
|
|
if (!window.location.pathname.includes('/dashboard')) {
|
|
toast.error('You must be logged in to submit an event request');
|
|
}
|
|
throw new Error('You must be logged in to submit an event request');
|
|
}
|
|
|
|
// Create the event request record
|
|
const userId = auth.getUserId();
|
|
if (!userId) {
|
|
// Don't show error toast on dashboard page for unauthenticated users
|
|
if (auth.isAuthenticated() || !window.location.pathname.includes('/dashboard')) {
|
|
toast.error('User ID not found');
|
|
}
|
|
throw new Error('User ID not found');
|
|
}
|
|
|
|
// Prepare data for submission
|
|
const submissionData = {
|
|
requested_user: userId,
|
|
name: formData.name,
|
|
location: formData.location,
|
|
start_date_time: new Date(formData.start_date_time).toISOString(),
|
|
end_date_time: formData.end_date_time ? new Date(formData.end_date_time).toISOString() : new Date(formData.start_date_time).toISOString(),
|
|
event_description: formData.event_description,
|
|
flyers_needed: formData.flyers_needed,
|
|
photography_needed: formData.photography_needed,
|
|
as_funding_required: formData.needs_as_funding,
|
|
food_drinks_being_served: formData.food_drinks_being_served,
|
|
itemized_invoice: formData.itemized_invoice,
|
|
flyer_type: formData.flyer_type,
|
|
other_flyer_type: formData.other_flyer_type,
|
|
flyer_advertising_start_date: formData.flyer_advertising_start_date ? new Date(formData.flyer_advertising_start_date).toISOString() : '',
|
|
flyer_additional_requests: formData.flyer_additional_requests,
|
|
required_logos: formData.required_logos,
|
|
advertising_format: formData.advertising_format,
|
|
will_or_have_room_booking: formData.will_or_have_room_booking,
|
|
expected_attendance: formData.expected_attendance,
|
|
needs_graphics: formData.needs_graphics,
|
|
needs_as_funding: formData.needs_as_funding,
|
|
invoice_data: {
|
|
items: formData.invoiceData.items.map(item => ({
|
|
item: item.description,
|
|
quantity: item.quantity,
|
|
unit_price: item.unitPrice
|
|
})),
|
|
taxAmount: formData.invoiceData.taxAmount,
|
|
tipAmount: formData.invoiceData.tipAmount,
|
|
total: formData.invoiceData.total,
|
|
vendor: formData.invoiceData.vendor
|
|
},
|
|
};
|
|
|
|
// Create the record using the Update service
|
|
// This will send the data to the server
|
|
const record = await update.create('event_request', submissionData);
|
|
|
|
// Force sync the event requests collection to update IndexedDB
|
|
await dataSync.syncCollection(Collections.EVENT_REQUESTS);
|
|
|
|
// Upload files if they exist
|
|
if (formData.other_logos.length > 0) {
|
|
await fileManager.uploadFiles('event_request', record.id, 'other_logos', formData.other_logos);
|
|
}
|
|
|
|
if (formData.room_booking) {
|
|
await fileManager.uploadFile('event_request', record.id, 'room_booking', formData.room_booking);
|
|
}
|
|
|
|
// Upload multiple invoice files
|
|
if (formData.invoice_files && formData.invoice_files.length > 0) {
|
|
await fileManager.appendFiles('event_request', record.id, 'invoice_files', formData.invoice_files);
|
|
|
|
// For backward compatibility, also upload the first file as the main invoice
|
|
if (formData.invoice || formData.invoice_files[0]) {
|
|
const mainInvoice = formData.invoice || formData.invoice_files[0];
|
|
await fileManager.uploadFile('event_request', record.id, 'invoice', mainInvoice);
|
|
}
|
|
} else if (formData.invoice) {
|
|
await fileManager.uploadFile('event_request', record.id, 'invoice', formData.invoice);
|
|
}
|
|
|
|
// Clear form data from localStorage
|
|
localStorage.removeItem('eventRequestFormData');
|
|
|
|
// Keep success toast for form submission since it's a user action
|
|
toast.success('Event request submitted successfully!');
|
|
|
|
// Reset form
|
|
resetForm();
|
|
|
|
// Switch to the submissions tab
|
|
const submissionsTab = document.getElementById('submissions-tab');
|
|
if (submissionsTab) {
|
|
submissionsTab.click();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error submitting event request:', error);
|
|
toast.error('Failed to submit event request. Please try again.');
|
|
setError('Failed to submit event request. Please try again.');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
// Validate PR Section
|
|
const validatePRSection = () => {
|
|
if (formData.flyer_type.length === 0) {
|
|
toast.error('Please select at least one flyer type');
|
|
return false;
|
|
}
|
|
|
|
if (formData.flyer_type.includes('other') && !formData.other_flyer_type) {
|
|
toast.error('Please specify the other flyer type');
|
|
return false;
|
|
}
|
|
|
|
if (formData.flyer_type.some(type =>
|
|
type === 'digital_with_social' ||
|
|
type === 'physical_with_advertising' ||
|
|
type === 'newsletter'
|
|
) && !formData.flyer_advertising_start_date) {
|
|
toast.error('Please specify when to start advertising');
|
|
return false;
|
|
}
|
|
|
|
if (formData.required_logos.includes('OTHER') && (!formData.other_logos || formData.other_logos.length === 0)) {
|
|
toast.error('Please upload your logo files');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.advertising_format) {
|
|
toast.error('Please select a format');
|
|
return false;
|
|
}
|
|
|
|
if (formData.photography_needed === null || formData.photography_needed === undefined) {
|
|
toast.error('Please specify if photography is needed');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Validate Event Details Section
|
|
const validateEventDetailsSection = () => {
|
|
let valid = true;
|
|
const errors: string[] = [];
|
|
|
|
if (!formData.name || formData.name.trim() === '') {
|
|
errors.push('Event name is required');
|
|
valid = false;
|
|
}
|
|
|
|
if (!formData.event_description || formData.event_description.trim() === '') {
|
|
errors.push('Event description is required');
|
|
valid = false;
|
|
}
|
|
|
|
if (!formData.start_date_time || formData.start_date_time.trim() === '') {
|
|
errors.push('Event start date and time is required');
|
|
valid = false;
|
|
}
|
|
|
|
if (!formData.end_date_time) {
|
|
errors.push('Event end time is required');
|
|
valid = false;
|
|
}
|
|
|
|
if (!formData.location || formData.location.trim() === '') {
|
|
errors.push('Event location is required');
|
|
valid = false;
|
|
}
|
|
|
|
if (formData.will_or_have_room_booking === undefined) {
|
|
errors.push('Room booking status is required');
|
|
valid = false;
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
setError(errors[0]);
|
|
return false;
|
|
}
|
|
|
|
return valid;
|
|
};
|
|
|
|
// Validate TAP Form Section
|
|
const validateTAPFormSection = () => {
|
|
// Verify that all required fields are filled
|
|
if (!formData.will_or_have_room_booking && formData.will_or_have_room_booking !== false) {
|
|
toast.error('Please indicate whether you will or have a room booking');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.expected_attendance || formData.expected_attendance <= 0) {
|
|
toast.error('Please enter a valid expected attendance');
|
|
return false;
|
|
}
|
|
|
|
// Only require room booking file if will_or_have_room_booking is true
|
|
if (formData.will_or_have_room_booking && !formData.room_booking) {
|
|
toast.error('Please upload your room booking confirmation');
|
|
return false;
|
|
}
|
|
|
|
if (!formData.food_drinks_being_served && formData.food_drinks_being_served !== false) {
|
|
toast.error('Please indicate whether food/drinks will be served');
|
|
return false;
|
|
}
|
|
|
|
// Validate AS funding question if food is being served
|
|
if (formData.food_drinks_being_served && formData.needs_as_funding === undefined) {
|
|
toast.error('Please indicate whether you need AS funding');
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Validate AS Funding Section
|
|
const validateASFundingSection = () => {
|
|
if (formData.as_funding_required) {
|
|
// Check if invoice data is present and has items
|
|
if (!formData.invoiceData || !formData.invoiceData.items || formData.invoiceData.items.length === 0) {
|
|
setError('Please add at least one item to your invoice');
|
|
return false;
|
|
}
|
|
|
|
// Calculate the total budget from invoice items
|
|
const totalBudget = formData.invoiceData.items.reduce(
|
|
(sum, item) => sum + (item.unitPrice * item.quantity), 0
|
|
);
|
|
|
|
// Check if the budget exceeds the maximum allowed ($5000 cap regardless of attendance)
|
|
const maxBudget = Math.min(formData.expected_attendance * 10, 5000);
|
|
if (totalBudget > maxBudget) {
|
|
setError(`Your budget (${totalBudget.toFixed(2)} dollars) exceeds the maximum allowed (${maxBudget} dollars). The absolute maximum is $5,000.`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Validate all sections before submission
|
|
const validateAllSections = () => {
|
|
// We no longer forcibly set end_date_time to match start_date_time
|
|
// The end time is now configured separately with the same date
|
|
|
|
if (!validateEventDetailsSection()) {
|
|
setCurrentStep(1);
|
|
return false;
|
|
}
|
|
|
|
// Validate TAP Form
|
|
if (!validateTAPFormSection()) {
|
|
return false;
|
|
}
|
|
|
|
// Validate PR Section if needed
|
|
if (formData.needs_graphics && !validatePRSection()) {
|
|
return false;
|
|
}
|
|
|
|
// Validate AS Funding if needed
|
|
if (formData.food_drinks_being_served && formData.needs_as_funding && !validateASFundingSection()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// Handle next button click with validation
|
|
const handleNextStep = (nextStep: number) => {
|
|
let isValid = true;
|
|
|
|
// Validate current section before proceeding
|
|
if (currentStep === 2 && formData.needs_graphics) {
|
|
isValid = validatePRSection();
|
|
} else if (currentStep === 3) {
|
|
isValid = validateEventDetailsSection();
|
|
} else if (currentStep === 4) {
|
|
isValid = validateTAPFormSection();
|
|
} else if (currentStep === 5 && formData.food_drinks_being_served && formData.needs_as_funding) {
|
|
isValid = validateASFundingSection();
|
|
}
|
|
|
|
if (!isValid) {
|
|
return; // Don't proceed if validation fails
|
|
}
|
|
|
|
// If we're moving from step 4 to step 5
|
|
if (currentStep === 4 && nextStep === 5) {
|
|
// If food and drinks aren't being served or if AS funding isn't needed, skip to step 6 (review)
|
|
if (!formData.food_drinks_being_served || !formData.needs_as_funding) {
|
|
nextStep = 6;
|
|
}
|
|
}
|
|
|
|
// Set the current step
|
|
setCurrentStep(nextStep);
|
|
|
|
// If moving to the review step, mark the form as reviewed
|
|
// but don't submit it automatically
|
|
if (nextStep === 6) {
|
|
setFormData(prevData => ({
|
|
...prevData,
|
|
formReviewed: true
|
|
}));
|
|
}
|
|
};
|
|
|
|
// Handle form submission with validation
|
|
const handleSubmitWithValidation = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
// If we're on the review step, we've already validated all sections
|
|
// Only submit if the user explicitly clicks the submit button
|
|
if (currentStep === 6 && formData.formReviewed) {
|
|
handleSubmit(e);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, validate all sections before proceeding to the review step
|
|
if (validateAllSections()) {
|
|
// If we're not on the review step, go to the review step instead of submitting
|
|
handleNextStep(6);
|
|
}
|
|
};
|
|
|
|
// Render the current step
|
|
const renderCurrentSection = () => {
|
|
// Step 1: Ask if they need graphics from the design team
|
|
if (currentStep === 1) {
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
className="space-y-6"
|
|
>
|
|
<CustomAlert
|
|
type="warning"
|
|
title="Multiple Events Notice"
|
|
message="If you have multiple events, you must submit a separate TAP form for each one. Multiple-day events require individual submissions for each day."
|
|
icon="heroicons:exclamation-triangle"
|
|
className="mb-4"
|
|
/>
|
|
|
|
<h2 className="text-2xl font-bold mb-4 text-primary">Event Request Form</h2>
|
|
|
|
<div className="bg-base-300/50 p-4 rounded-lg mb-6">
|
|
<p className="text-sm">
|
|
Welcome to the IEEE UCSD Event Request Form. This form will help you request PR materials,
|
|
provide event details, and request AS funding if needed.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="bg-base-200/50 p-6 rounded-lg">
|
|
<h3 className="text-xl font-semibold mb-4">Do you need graphics from the design team?</h3>
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
<button
|
|
className={`btn btn-lg ${formData.needs_graphics ? 'btn-primary' : 'btn-outline'} flex-1`}
|
|
onClick={() => {
|
|
setFormData({ ...formData, needs_graphics: true, flyers_needed: true });
|
|
setCurrentStep(2);
|
|
}}
|
|
>
|
|
Yes
|
|
</button>
|
|
<button
|
|
className={`btn btn-lg ${!formData.needs_graphics && formData.needs_graphics !== null ? 'btn-primary' : 'btn-outline'} flex-1`}
|
|
onClick={() => {
|
|
setFormData({ ...formData, needs_graphics: false, flyers_needed: false });
|
|
setCurrentStep(3);
|
|
}}
|
|
>
|
|
No
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
// Step 2: PR Section (if they need graphics)
|
|
if (currentStep === 2) {
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
className="space-y-6"
|
|
>
|
|
<PRSection formData={formData} onDataChange={handleSectionDataChange} />
|
|
|
|
<div className="flex justify-between mt-8">
|
|
<button className="btn btn-outline" onClick={() => setCurrentStep(1)}>
|
|
Back
|
|
</button>
|
|
<button className="btn btn-primary" onClick={() => handleNextStep(3)}>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
// Step 3: Event Details Section
|
|
if (currentStep === 3) {
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
className="space-y-6"
|
|
>
|
|
<EventDetailsSection formData={formData} onDataChange={handleSectionDataChange} />
|
|
|
|
<div className="flex justify-between mt-8">
|
|
<button className="btn btn-outline" onClick={() => setCurrentStep(formData.needs_graphics ? 2 : 1)}>
|
|
Back
|
|
</button>
|
|
<button className="btn btn-primary" onClick={() => handleNextStep(4)}>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
// Step 4: TAP Form Section
|
|
if (currentStep === 4) {
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
className="space-y-6"
|
|
>
|
|
<TAPFormSection formData={formData} onDataChange={handleSectionDataChange} />
|
|
|
|
<div className="flex justify-between mt-8">
|
|
<button className="btn btn-outline" onClick={() => setCurrentStep(3)}>
|
|
Back
|
|
</button>
|
|
<button className="btn btn-primary" onClick={() => handleNextStep(5)}>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
// Step 5: AS Funding Section
|
|
if (currentStep === 5) {
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
className="space-y-6"
|
|
>
|
|
<div className="bg-base-200/50 p-6 rounded-lg mb-6">
|
|
<h3 className="text-xl font-semibold mb-4">AS Funding Details</h3>
|
|
<p className="mb-4">Please provide the necessary information for your AS funding request.</p>
|
|
</div>
|
|
|
|
<ASFundingSection formData={formData} onDataChange={handleSectionDataChange} />
|
|
|
|
<div className="flex justify-between mt-8">
|
|
<button className="btn btn-outline" onClick={() => setCurrentStep(4)}>
|
|
Back
|
|
</button>
|
|
<button className="btn btn-primary" onClick={() => handleNextStep(6)}>
|
|
Next
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
// Step 6: Review Form
|
|
if (currentStep === 6) {
|
|
return (
|
|
<motion.div
|
|
initial="hidden"
|
|
animate="visible"
|
|
variants={containerVariants}
|
|
className="space-y-6"
|
|
>
|
|
<h2 className="text-2xl font-bold mb-4 text-primary">Review Your Event Request</h2>
|
|
|
|
<div className="bg-base-300/50 p-4 rounded-lg mb-6">
|
|
<p className="text-sm">
|
|
Please review all information carefully before submitting. You can go back to any section to make changes if needed.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="bg-base-200/50 p-6 rounded-lg">
|
|
<EventRequestFormPreview formData={formData} isModal={false} />
|
|
|
|
<div className="divider my-6">Ready to Submit?</div>
|
|
|
|
<CustomAlert
|
|
type="info"
|
|
title="Important Note"
|
|
message="Once submitted, you'll need to notify PR and/or Coordinators in the #-events Slack channel."
|
|
icon="heroicons:information-circle"
|
|
className="mb-6"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-between mt-8">
|
|
<button
|
|
className="btn btn-outline"
|
|
onClick={() => {
|
|
// Skip the AS Funding section if not needed
|
|
if (!formData.food_drinks_being_served || !formData.needs_as_funding) {
|
|
setCurrentStep(4); // Go back to TAP Form section
|
|
} else {
|
|
setCurrentStep(5); // Go back to AS Funding section
|
|
}
|
|
}}
|
|
>
|
|
Back
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-success btn-lg"
|
|
onClick={handleSubmitWithValidation}
|
|
disabled={isSubmitting}
|
|
>
|
|
{isSubmitting ? (
|
|
<>
|
|
<span className="loading loading-spinner"></span>
|
|
Submitting...
|
|
</>
|
|
) : (
|
|
'Submit Event Request'
|
|
)}
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<motion.div
|
|
variants={containerVariants}
|
|
initial="hidden"
|
|
animate="visible"
|
|
className="max-w-4xl mx-auto"
|
|
>
|
|
<form
|
|
onSubmit={(e) => {
|
|
// Prevent default form submission behavior
|
|
e.preventDefault();
|
|
// Only submit if the user explicitly clicks the submit button
|
|
// The actual submission is handled by handleSubmitWithValidation
|
|
}}
|
|
className="space-y-6"
|
|
>
|
|
{error && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -20, scale: 0.95 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
exit={{ opacity: 0, y: 20, scale: 0.95 }}
|
|
>
|
|
<CustomAlert
|
|
type="error"
|
|
title="Error"
|
|
message={error}
|
|
icon="heroicons:exclamation-triangle"
|
|
/>
|
|
</motion.div>
|
|
)}
|
|
|
|
{/* Progress indicator */}
|
|
<div className="w-full mb-6">
|
|
<div className="flex justify-between mb-2">
|
|
<span className="text-sm font-medium">Step {currentStep} of 6</span>
|
|
<span className="text-sm font-medium">{Math.min(Math.round((currentStep / 6) * 100), 100)}% complete</span>
|
|
</div>
|
|
<div className="w-full bg-base-300 rounded-full h-2.5">
|
|
<div
|
|
className="bg-primary h-2.5 rounded-full transition-all duration-300"
|
|
style={{ width: `${Math.min((currentStep / 6) * 100, 100)}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Current section */}
|
|
<AnimatePresence mode="wait">
|
|
{renderCurrentSection()}
|
|
</AnimatePresence>
|
|
</form>
|
|
</motion.div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default EventRequestForm;
|