diff --git a/src/components/KeywordConfirmationModal.tsx b/src/components/KeywordConfirmationModal.tsx index b3a72c5..c7790ef 100644 --- a/src/components/KeywordConfirmationModal.tsx +++ b/src/components/KeywordConfirmationModal.tsx @@ -1,11 +1,22 @@ -import { X } from "lucide-react"; import { useState, type ChangeEvent } from "react"; +import { Upload, X, Trash2, XCircle, User } from "lucide-react"; interface KeywordConfirmationModalProps { onClose: () => void; + initialStep?: number; } interface FormData { + // Step 1: Logo & Socials + firstName: string; + socialProfiles: string[]; + uploadedLogo: object; + // Step 2: Media Assets + notes: string; + tShirtSize: string; + uploadedFiles: Array<{ name: string; size: string; progress?: number }>; + + // Step 3: Payment nameOnCard: string; cardNumber: string; expirationDate: string; @@ -18,30 +29,23 @@ interface FormData { } interface FormErrors { + firstName?: string; + uploadedLogo?: string; + tShirtSize?: string; nameOnCard?: string; cardNumber?: string; expirationDate?: string; securityCode?: string; billingAddress?: string; + } const US_STATES = [ - "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware", - "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", - "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi", - "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", - "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", - "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", - "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming" + "Alabama", "Alaska", "Arizona", "Arkansas" ]; const US_CITIES = [ - "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", - "San Diego", "Dallas", "San Jose", "Austin", "Jacksonville", "Fort Worth", "Columbus", - "Charlotte", "San Francisco", "Indianapolis", "Seattle", "Denver", "Boston", "Nashville", - "Detroit", "Oklahoma City", "Portland", "Las Vegas", "Memphis", "Louisville", "Baltimore", - "Milwaukee", "Albuquerque", "Tucson", "Fresno", "Mesa", "Sacramento", "Atlanta", "Kansas City", - "Colorado Springs", "Miami", "Raleigh", "Omaha", "Long Beach", "Virginia Beach", "Oakland" + "New York", "Los Angeles", "Chicago" ]; const BeaconIcon = () => ( @@ -79,6 +83,11 @@ const CreditCardIcon = () => ( ); +const UploadIcon = () => ( + + + +); const DropdownArrow = () => ( @@ -86,8 +95,51 @@ const DropdownArrow = () => ( ); -export default function KeywordConfirmationModal({ onClose }: KeywordConfirmationModalProps) { +const CompanyIcon = () => ( + + + + + + + + + +); + +const GlobeIcon = () => ( + + + + + +); + +const HeartIcon = () => ( + + + +); + + +// export default function KeywordConfirmationModal({ onClose }: KeywordConfirmationModalProps) { +export default function KeywordConfirmationModal({ onClose, initialStep = 1 }: KeywordConfirmationModalProps) { + const [formData, setFormData] = useState({ + // Step 1: Logo & Socials + firstName: "", + socialProfiles: [""], + uploadedLogo: {}, + + // Step 2: Media Assets + notes: "", + tShirtSize: "Medium (US M)", + uploadedFiles: initialStep === 2 ? [ + { name: "Document.docx", size: "234kb" }, + { name: "Resume.pdf", size: "4Mb", progress: 73 } + ] : [], + + // Step 3: Payment nameOnCard: "", cardNumber: "", expirationDate: "", @@ -99,7 +151,58 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio sameAsShipping: true, }); + const [errors, setErrors] = useState({}); + const [currentStep, setCurrentStep] = useState(initialStep); + + // Step 1 & 2 helper functions + const addSocialProfile = () => { + setFormData(prev => ({ ...prev, socialProfiles: [...prev.socialProfiles, ""] })); + }; + + const updateSocialProfile = (index: number, value: string) => { + const updated = [...formData.socialProfiles]; + updated[index] = value; + setFormData(prev => ({ ...prev, socialProfiles: updated })); + }; + + const removeFile = (index: number) => { + setFormData(prev => ({ + ...prev, + uploadedFiles: prev.uploadedFiles.filter((_, i) => i !== index) + })); + }; + + const handleFileUpload = (e: React.ChangeEvent, logo: string) => { + const files = e.target.files; + + + if (files) { + const newFiles = Array.from(files).map((file) => ({ + name: file.name, + size: `${(file.size / 1024).toFixed(0)}kb`, + progress: Math.random() > 0.5 ? 73 : undefined, + })); + setFormData(prev => { + if (logo) { + return { + ...prev, + uploadedLogo: newFiles[0] + }; + } else { + return { + ...prev, + uploadedFiles: [ + ...(Array.isArray(prev.uploadedFiles) ? prev.uploadedFiles : []), + ...newFiles + ] + }; + } + }); + } + + }; + const formatCardNumber = (value: string) => { const numbers = value.replace(/\s/g, ""); @@ -119,7 +222,7 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio const cleaned = number.replace(/\s/g, ""); if (cleaned.length !== 16) return false; if (!cleaned.startsWith("4")) return false; - + let sum = 0; let isEven = false; for (let i = cleaned.length - 1; i >= 0; i--) { @@ -134,7 +237,7 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio return sum % 10 === 0; }; - const handleInputChange = (e: ChangeEvent) => { + const handleInputChange = (e: ChangeEvent) => { const { name, value } = e.target; let formattedValue = value; @@ -147,7 +250,7 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio } setFormData((prev) => ({ ...prev, [name]: formattedValue })); - + if (errors[name as keyof FormErrors]) { setErrors((prev) => ({ ...prev, [name]: undefined })); } @@ -157,7 +260,44 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio setFormData((prev) => ({ ...prev, sameAsShipping: !prev.sameAsShipping })); }; - const validateForm = (): boolean => { + // The approach used could be improved for clarity and consistency with the rest of your codebase. + + // 1. You are mutating the `errors` object directly, which is not recommended in React, + // especially if errors is managed by useState, because it will not trigger a re-render. + // 2. It's better to create a new error object (like in your validateStep3) + // 3. The comment and the logic don't match - "firstName is optional" but then you require it. + + // Recommended rewrite: + const validateStep1 = (): boolean => { + const newErrors: FormErrors = {}; + + if (!formData.firstName.trim()) { + newErrors.firstName = "Name is required"; + } + + if (!formData.uploadedLogo) { + newErrors.uploadedLogo = "Logo is required"; + } + + setErrors(newErrors); + + return Object.keys(newErrors).length === 0; + }; + + const validateStep2 = (): boolean => { + const newErrors: FormErrors = {}; + + if (!formData.tShirtSize.trim()) { + newErrors.tShirtSize = "T-shirt size is required"; + } + + setErrors(newErrors); + + return Object.keys(newErrors).length === 0; + }; + + const validateStep3 = (): boolean => { + // Step 3 validation - payment fields are required const newErrors: FormErrors = {}; if (!formData.nameOnCard.trim()) { @@ -197,8 +337,24 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio return Object.keys(newErrors).length === 0; }; + const nextStep = () => { + if (currentStep < 3) { + let canAdvance = false; + + if (currentStep === 1) { + canAdvance = validateStep1(); + } else if (currentStep === 2) { + canAdvance = validateStep2(); + } + + if (canAdvance) { + setCurrentStep(currentStep + 1); + } + } + }; + const handleSubmit = () => { - if (validateForm()) { + if (validateStep3()) { console.log("Form submitted:", formData); onClose(); } @@ -206,264 +362,595 @@ export default function KeywordConfirmationModal({ onClose }: KeywordConfirmatio return (
-
+ {/*
-
+
-
-

Keyword Confirmation

+
+

+ Let's Light the Beacons +

+

+ Alright, this is the part where we make it official. Like moving in together, but with backlinks. +

-
- -
-
-
Your You're lighting Beacons for:
- - - - -
-
-
- - Beverly Hills Medical SPA -
- Brand Visibility -
-
-
-
-
- - LA Medical SPA -
-
- Brand Visibility -
-
-
- -
-

Looking strong. Like, "top of Google and AI mentions" strong.

-
-
- -
-
-
-
- -
-
-
- Credit Card Info - -
-

We keep it secure. Scout's honor.

-
-
- -
-
- -
- - {errors.nameOnCard && {errors.nameOnCard}} -
- -
-
- -
- - {errors.cardNumber && {errors.cardNumber}} -
- -
-
-
- -
- - {errors.expirationDate && {errors.expirationDate}} -
-
-
- -
- - {errors.securityCode && {errors.securityCode}} -
-
-
- -
-
-
- -
- Address -
- -
-
- -
- - {errors.billingAddress && {errors.billingAddress}} -
- -
-
-
- -
-
- -
- -
-
-
-
-
- -
-
- -
- -
-
-
-
-
- -
- { - const value = e.target.value.replace(/\D/g, "").slice(0, 5); - setFormData((prev) => ({ ...prev, zipCode: value })); - }} - placeholder="12345" - className="h-8 px-2.5 rounded-lg border border-grey-300 bg-white text-body-m text-grey-900 placeholder:text-grey-600 shadow-[0_0_2px_0_rgba(5,11,18,0.12),0_1px_4px_-2px_rgba(24,39,75,0.02),0_4px_4px_-2px_rgba(24,39,75,0.06),0_0_0_1px_#E9EAF1] focus:outline-none focus:ring-2 focus:ring-primary-500" - /> -
-
- -
- {/* */} - - - (Because Merch.) -
-
-
-
- +
*/} + {/* Modal Header */}
- - - -
-
+ + {/* Step Indicators */} +
+
+
= 1 ? 'bg-[#E6ECFB]' : 'border border-[#DFE2EB]'}`}> +
= 1 ? '' : 'bg-[#DFE1EB]'} pt-[5px] pr-[12px] pb-[5px] pl-[9px]`} + > + = 1 ? 'text-white bg-[#4C60E5]' : 'text-[#65677D]'}`}>1 +
+ = 1 ? 'text-[#272735]' : 'text-[#787B91]'}`}>Logo & Socials +
+ + + = 2 ? "#4C60E5" : "#C3C5D5"} /> + = 2 ? "#4C60E5" : "#C3C5D5"} /> + = 2 ? "#4C60E5" : "#C3C5D5"} /> + + +
= 2 ? 'bg-[#E6ECFB]' : 'border border-[#DFE2EB]'}`}> +
= 1 ? '' : 'bg-[#DFE1EB]'} pt-[5px] pr-[12px] pb-[5px] pl-[9px]`}> + = 2 ? 'text-white bg-[#4C60E5]' : 'text-[#65677D] bg-[#DFE1EB]'}`}>2 +
+ = 2 ? 'text-[#272735]' : 'text-[#787B91]'}`}>Media Assets +
+ + + = 3 ? "#4C60E5" : "#C3C5D5"} /> + = 3 ? "#4C60E5" : "#C3C5D5"} /> + = 3 ? "#4C60E5" : "#C3C5D5"} /> + + +
= 3 ? 'bg-[#E6ECFB]' : 'border border-[#DFE2EB]'}`}> +
= 1 ? '' : 'bg-[#DFE1EB]'} pt-[5px] pr-[12px] pb-[5px] pl-[9px]`}> + = 3 ? 'text-white bg-[#4C60E5]' : 'text-[#65677D] bg-[#DFE1EB]'}`}>3 +
+ = 3 ? 'text-[#272735]' : 'text-[#787B91]'}`}>Payment +
+
+
-
-
-

- Sidenote: - You now have access to unlimited website analyses. If you run multiple businesses or manage clients, you can handle everything from your dashboard, separate campaigns, organized by brand. It's like project management… but fun. -

-
+ + {/* Content */} +
+ {currentStep === 1 && ( +
+ {/* Business Info Card */} +
+
+
Your Business
+ + + +
+
+ + Beverly Hills Medical Spa +
+
+
+ + www.bhmedicalspas.com +
+
+
+
+ + {/* Section Header */} +
+
+ +
+

Let's Get To Know You

+
+ + {/* Form Fields */} +
+ {/* First Name */} +
+
+
+ +
+

We like to know who to thank when you go viral.

+
+ + {errors.firstName &&

{errors.firstName}

} +
+ + {/* Upload Logo */} +
+
+
+ +
+

We'll treat it like it belongs in the Louvre.

+
+ + {errors.uploadedLogo &&

{errors.uploadedLogo}

} +
+ + {/* Social Media Profiles */} +
+
+
+ + Optional +
+

Yes, even that TikTok you said you'd update in 2022.

+
+ {formData.socialProfiles.map((profile, index) => ( + updateSocialProfile(index, e.target.value)} + placeholder="Enter name..." + className="h-8 px-2.5 rounded-lg border border-gray-300 bg-white shadow-[0_0_2px_0_rgba(5,11,18,0.12),0_1px_4px_-2px_rgba(24,39,75,0.02),0_4px_4px_-2px_rgba(24,39,75,0.06)] text-sm leading-5 placeholder:text-[#999BAD] focus:outline-none focus:ring-2 focus:ring-[#4C60E5]" + /> + ))} + +
+
+
+ )} + + {currentStep === 2 && ( +
+ {/* Section Header */} +
+
+ +
+

Let's Get To Know You

+
+ + {/* Form Fields */} +
+ {/* Image/Video Assets */} +
+
+
+ + Optional +
+

+ Product shots, commercials, brand photoshoots, CEO karaoke footage - if it's got pixels, we want it. +

+
+
+
+ Upload + + + +
+ +
+ + {/* Uploaded Files */} + {formData.uploadedFiles.length > 0 && ( +
+ {formData.uploadedFiles.map((file, index) => ( +
+ {file.progress && ( +
+ )} +
+ + + + + + {file.name} + ({file.size}) + +
+
+ {file.progress && {file.progress}%} + +
+
+ ))} +
+ )} +
+ + {/* Anything You Want Us To Know */} +
+
+
+ + Optional +
+

+ Business goals? Secret sauce? Favorite pizza topping? We don't judge. Unless it's anchovies. +

+
+