Initial commit: Complete Pennyzo Frontend with waitlist form implementation
- Added React TypeScript frontend with Vite build system - Implemented luxury-themed UI with custom Tailwind colors - Created comprehensive waitlist form with conditional logic: * Personal information (name, email) * Company details (name, type, GST registration) * Conditional CIN/PAN fields based on company type and GST status * Industry selection and goals questionnaire - Added form validation and error handling - Integrated waitlist form with hero section and live counter - Implemented responsive design with elegant animations - Added proper gitignore for frontend project
This commit is contained in:
456
src/components/WaitlistForm.tsx
Normal file
456
src/components/WaitlistForm.tsx
Normal file
@@ -0,0 +1,456 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface WaitlistFormProps {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
companyName: string;
|
||||
companyType: string;
|
||||
gstRegistered: string;
|
||||
cin: string;
|
||||
panNumber: string;
|
||||
industry: string;
|
||||
goals: string;
|
||||
}
|
||||
|
||||
const WaitlistForm: React.FC<WaitlistFormProps> = ({ isVisible, onClose, onBack }) => {
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
companyName: '',
|
||||
companyType: '',
|
||||
gstRegistered: '',
|
||||
cin: '',
|
||||
panNumber: '',
|
||||
industry: '',
|
||||
goals: ''
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errors, setErrors] = useState<Partial<FormData>>({});
|
||||
|
||||
const companyTypes = [
|
||||
'Pvt Ltd',
|
||||
'LLP',
|
||||
'Partnership',
|
||||
'Sole Proprietorship',
|
||||
'One Person Company (OPC)',
|
||||
'Public Limited Company',
|
||||
'Other'
|
||||
];
|
||||
|
||||
const industries = [
|
||||
'Information Technology',
|
||||
'Finance & Banking',
|
||||
'E-commerce',
|
||||
'Manufacturing',
|
||||
'Healthcare',
|
||||
'Education',
|
||||
'Real Estate',
|
||||
'Retail',
|
||||
'Consulting',
|
||||
'Marketing & Advertising',
|
||||
'Food & Beverage',
|
||||
'Transportation',
|
||||
'Other'
|
||||
];
|
||||
|
||||
const handleInputChange = (field: keyof FormData, value: string) => {
|
||||
setFormData(prev => {
|
||||
const newData = { ...prev, [field]: value };
|
||||
|
||||
// Clear conditional fields when company type changes
|
||||
if (field === 'companyType') {
|
||||
// Clear both fields when company type changes
|
||||
newData.cin = '';
|
||||
newData.panNumber = '';
|
||||
}
|
||||
|
||||
// Clear conditional fields when GST registration changes to "yes"
|
||||
if (field === 'gstRegistered' && value === 'yes') {
|
||||
newData.cin = '';
|
||||
newData.panNumber = '';
|
||||
}
|
||||
|
||||
return newData;
|
||||
});
|
||||
|
||||
// Clear error when user starts typing
|
||||
if (errors[field]) {
|
||||
setErrors(prev => ({ ...prev, [field]: '' }));
|
||||
}
|
||||
};
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: Partial<FormData> = {};
|
||||
|
||||
if (!formData.firstName.trim()) newErrors.firstName = 'First name is required';
|
||||
if (!formData.lastName.trim()) newErrors.lastName = 'Last name is required';
|
||||
if (!formData.email.trim()) {
|
||||
newErrors.email = 'Email is required';
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
newErrors.email = 'Please enter a valid email';
|
||||
}
|
||||
if (!formData.companyName.trim()) newErrors.companyName = 'Company name is required';
|
||||
if (!formData.companyType) newErrors.companyType = 'Company type is required';
|
||||
if (!formData.gstRegistered) newErrors.gstRegistered = 'GST registration status is required';
|
||||
|
||||
// Conditional validation - only when GST is NOT registered
|
||||
if (formData.gstRegistered === 'no') {
|
||||
if (['Pvt Ltd', 'LLP'].includes(formData.companyType)) {
|
||||
// For Private Limited and LLP - CIN is required only when GST is not registered
|
||||
if (!formData.cin.trim()) newErrors.cin = 'CIN is required for Private Limited and LLP companies';
|
||||
} else if (['Partnership', 'Sole Proprietorship', 'One Person Company (OPC)', 'Other'].includes(formData.companyType)) {
|
||||
// For Partnership, Proprietorship, Solo/Individual - PAN is required only when GST is not registered
|
||||
if (!formData.panNumber.trim()) newErrors.panNumber = 'PAN Number is required for this company type';
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.industry) newErrors.industry = 'Industry is required';
|
||||
if (!formData.goals.trim()) newErrors.goals = 'Please tell us about your goals';
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!validateForm()) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate form submission
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
setIsLoading(false);
|
||||
console.log('Waitlist form submitted:', formData);
|
||||
// Handle success - maybe show success message or close form
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-deep-forest/80 backdrop-blur-sm">
|
||||
<div className="relative max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||
{/* Close Button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute -top-2 -right-2 w-8 h-8 bg-luxury-gold hover:bg-luxury-gold/90 text-deep-forest rounded-full flex items-center justify-center font-bold z-10 transition-all duration-200 shadow-lg"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="absolute -top-2 -left-2 w-8 h-8 bg-luxury-gold/20 hover:bg-luxury-gold/30 text-luxury-gold border border-luxury-gold/30 rounded-full flex items-center justify-center font-bold z-10 transition-all duration-200 shadow-lg"
|
||||
>
|
||||
←
|
||||
</button>
|
||||
|
||||
{/* Form Container */}
|
||||
<div className="bg-gradient-to-br from-deep-forest/95 to-rich-brown/90 backdrop-blur-2xl border border-luxury-gold/30 rounded-2xl shadow-2xl overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="bg-gradient-to-r from-luxury-gold/10 to-luxury-gold/5 px-8 py-6 border-b border-luxury-gold/20">
|
||||
<div className="text-center">
|
||||
<img
|
||||
src="/assets/Dark-Icon-cropped.svg"
|
||||
alt="Pennyzo Icon"
|
||||
className="w-16 h-16 mx-auto mb-4"
|
||||
/>
|
||||
<h2 className="text-luxury-gold font-luxury-serif text-2xl font-bold mb-2">
|
||||
Join Our Exclusive Waitlist
|
||||
</h2>
|
||||
<p className="text-cream/70 font-elegant-sans text-sm">
|
||||
Complete your application to secure your spot
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form */}
|
||||
<div className="p-8">
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Personal Information Section */}
|
||||
<div>
|
||||
<h3 className="text-luxury-gold font-elegant-sans text-lg font-semibold mb-4 flex items-center">
|
||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
Personal Information
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* First Name */}
|
||||
<div>
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
First Name <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.firstName}
|
||||
onChange={(e) => handleInputChange('firstName', e.target.value)}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.firstName ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
placeholder="Enter your first name"
|
||||
/>
|
||||
{errors.firstName && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.firstName}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Last Name */}
|
||||
<div>
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
Last Name <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.lastName}
|
||||
onChange={(e) => handleInputChange('lastName', e.target.value)}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.lastName ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
placeholder="Enter your last name"
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.lastName}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="mt-4">
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
Email Address <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleInputChange('email', e.target.value)}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.email ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
placeholder="Enter your email address"
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Company Information Section */}
|
||||
<div>
|
||||
<h3 className="text-luxury-gold font-elegant-sans text-lg font-semibold mb-4 flex items-center">
|
||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
Company Information
|
||||
</h3>
|
||||
|
||||
{/* Company Name */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
Company Name <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.companyName}
|
||||
onChange={(e) => handleInputChange('companyName', e.target.value)}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.companyName ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
placeholder="Enter your company name"
|
||||
/>
|
||||
{errors.companyName && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.companyName}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
{/* Company Type */}
|
||||
<div>
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
Company Type <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={formData.companyType}
|
||||
onChange={(e) => handleInputChange('companyType', e.target.value)}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.companyType ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
>
|
||||
<option value="">Select company type</option>
|
||||
{companyTypes.map(type => (
|
||||
<option key={type} value={type} className="bg-deep-forest text-cream">
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.companyType && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.companyType}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* GST Registered */}
|
||||
<div>
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
GST Registered <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<div className="flex space-x-4 pt-2">
|
||||
<label className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="gstRegistered"
|
||||
value="yes"
|
||||
checked={formData.gstRegistered === 'yes'}
|
||||
onChange={(e) => handleInputChange('gstRegistered', e.target.value)}
|
||||
className="w-4 h-4 text-luxury-gold bg-deep-forest/50 border-luxury-gold/20 focus:ring-luxury-gold/50 focus:ring-2"
|
||||
/>
|
||||
<span className="text-cream/90 font-elegant-sans text-sm">Yes</span>
|
||||
</label>
|
||||
<label className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
name="gstRegistered"
|
||||
value="no"
|
||||
checked={formData.gstRegistered === 'no'}
|
||||
onChange={(e) => handleInputChange('gstRegistered', e.target.value)}
|
||||
className="w-4 h-4 text-luxury-gold bg-deep-forest/50 border-luxury-gold/20 focus:ring-luxury-gold/50 focus:ring-2"
|
||||
/>
|
||||
<span className="text-cream/90 font-elegant-sans text-sm">No</span>
|
||||
</label>
|
||||
</div>
|
||||
{errors.gstRegistered && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.gstRegistered}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Conditional CIN Field - shows when company type is Pvt Ltd or LLP AND GST is NOT registered */}
|
||||
{formData.gstRegistered === 'no' && ['Pvt Ltd', 'LLP'].includes(formData.companyType) && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
CIN (Company Identification Number) <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.cin}
|
||||
onChange={(e) => handleInputChange('cin', e.target.value.toUpperCase())}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.cin ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
placeholder="e.g., L12345MH2020PLC123456"
|
||||
maxLength={21}
|
||||
/>
|
||||
{errors.cin && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.cin}</p>
|
||||
)}
|
||||
<p className="text-cream/50 text-xs mt-1 font-elegant-sans">
|
||||
21-digit Corporate Identity Number (required when GST not registered)
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Conditional PAN Number Field - shows for other company types AND GST is NOT registered */}
|
||||
{formData.gstRegistered === 'no' && ['Partnership', 'Sole Proprietorship', 'One Person Company (OPC)', 'Other'].includes(formData.companyType) && (
|
||||
<div className="mb-4">
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
PAN Number <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.panNumber}
|
||||
onChange={(e) => handleInputChange('panNumber', e.target.value.toUpperCase())}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.panNumber ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
placeholder="e.g., ABCDE1234F"
|
||||
maxLength={10}
|
||||
/>
|
||||
{errors.panNumber && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.panNumber}</p>
|
||||
)}
|
||||
<p className="text-cream/50 text-xs mt-1 font-elegant-sans">
|
||||
10-digit Permanent Account Number (required when GST not registered)
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Industry */}
|
||||
<div>
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
Industry <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={formData.industry}
|
||||
onChange={(e) => handleInputChange('industry', e.target.value)}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.industry ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
|
||||
>
|
||||
<option value="">Select your industry</option>
|
||||
{industries.map(industry => (
|
||||
<option key={industry} value={industry} className="bg-deep-forest text-cream">
|
||||
{industry}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.industry && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.industry}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Qualifying Question Section */}
|
||||
<div>
|
||||
<h3 className="text-luxury-gold font-elegant-sans text-lg font-semibold mb-4 flex items-center">
|
||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
Tell Us About Your Goals
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
|
||||
What do you want to achieve with Pennyzo? <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.goals}
|
||||
onChange={(e) => handleInputChange('goals', e.target.value)}
|
||||
rows={4}
|
||||
className={`w-full bg-deep-forest/50 border ${errors.goals ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans resize-none`}
|
||||
placeholder="Tell us about your business goals, challenges you're facing, or what you hope to achieve with our platform..."
|
||||
/>
|
||||
{errors.goals && (
|
||||
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.goals}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<div className="pt-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full bg-gradient-to-r from-luxury-gold to-luxury-gold/90 hover:from-luxury-gold/90 hover:to-luxury-gold text-deep-forest font-elegant-sans font-semibold py-4 px-4 rounded-xl transition-all duration-300 transform hover:scale-105 hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 relative overflow-hidden group"
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<div className="w-5 h-5 border-2 border-deep-forest border-t-transparent rounded-full animate-spin"></div>
|
||||
<span>Submitting Application...</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span className="relative z-10">Submit Application</span>
|
||||
<div className="absolute inset-0 bg-cream transform scale-x-0 group-hover:scale-x-100 transition-transform origin-left duration-300"></div>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Privacy Notice */}
|
||||
<div className="text-center text-cream/50 font-elegant-sans text-xs">
|
||||
By submitting this form, you agree to our terms of service and privacy policy.
|
||||
Your information will be kept confidential and used only for waitlist management.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WaitlistForm;
|
||||
Reference in New Issue
Block a user