diff --git a/components/MultiStepForm.tsx b/components/MultiStepForm.tsx index 4be4707d..077bdb17 100644 --- a/components/MultiStepForm.tsx +++ b/components/MultiStepForm.tsx @@ -1,97 +1,550 @@ import React, {useState} from "react"; import {useForm, SubmitHandler} from "react-hook-form"; + +// Updated Question type to support more field types and conditions type Question = { - name: string; - label: string; - type?: string; - options?: string[]; + name: string; + label: string; + type?: string; + options?: string[]; + optional?: boolean; + group?: boolean; + fields?: Question[]; + condition?: (values: any) => boolean; }; + +// Onboarding questions with conditional logic and grouping const questions: Question[] = [ - { - name: "firstName", - label: "First Name" - }, - { - name: "lastName", - label: "Last Name" - }, - { - name: "age", - label: "Age", - type: "number" - }, - { - name: "gender", - label: "Gender", - type: "select", - options: ["Male", "Female", "Other"], - }, + { + name: "firstName", + label: "Hi! What's your first name?", + }, + { + name: "location", + label: "Where are you located?", + group: true, + fields: [ + { name: "country", label: "Country" }, + { name: "zipCode", label: "Zip Code" }, + ], + }, + { + name: "gender", + label: "What's your gender?", + type: "select", + options: ["Man", "Woman", "See all"], + }, + { + name: "genderOther", + label: "How do you identify?", + type: "select", + options: [ + "Agender", + "Androgynous", + "Bigender", + "Cis Man", + "Cis Woman", + "Genderfluid", + "Genderqueer", + "Gender Nonconforming", + "Hijra", + "Intersex", + "Non-binary", + "Other gender", + "Pangender", + "Transfeminine", + "Transgender", + "Trans Man", + "Transmasculine", + "Transsexual", + "Trans Woman", + "Two Spirit" + ], + optional: true, + condition: (values) => values.gender === "See all", + }, + { + name: "birthday", + label: "When's your birthday?", + type: "date", + }, + { + name: "relationshipType", + label: "What kind of relationship are you looking for?", + type: "multiselect", + options: [ + "New friend", + "Short-term relationship", + "Long-term relationship", + "Other", + ], + }, + { + name: "kids", + label: "What are your ideal plans for children? (optional)", + type: "select", + options: [ + "Skip", + "Want someday", + "Don't want", + "Have and want more", + "Have and don't want more", + "Not sure yet", + "Have kids", + "Open to kids", + ], + optional: true, + condition: (values) => + ["Short-term dating", "Hookups", "Long-term dating"].includes( + values.relationshipType + ), + }, + { + name: "nonMonogamy", + label: "Non-Monogamy Options", + type: "select", + options: ["Monogamous", "Non-monogamous", "Open to either"], + condition: (values) => + ["Short-term dating", "Hookups", "Long-term dating"].includes( + values.relationshipType + ), + }, + { + name: "photos", + label: "Add photos (optional)", + type: "file", + optional: true, + }, + { + name: "about", + label: "Tell us about yourself", + type: "textarea", + }, + // Personality questions + { + name: "intenseOrCarefree", + label: "Which word describes you better?", + type: "select", + options: ["Intense", "Carefree"], + }, + { + name: "religion", + label: "How important is religion/God in your life?", + type: "select", + options: [ + "Not at all important", + "Slightly important", + "Moderately important", + "Very important", + "Extremely important" + ] + }, + { + name: "politics", + label: "Which best describes your political beliefs?", + type: "select", + options: [ + "Very liberal", + "Liberal", + "Moderate", + "Conservative", + "Very conservative", + "Other" + ] + }, + { + name: "introversion", + label: "How would you describe your social style?", + type: "select", + options: [ + "Very introverted", + "Somewhat introverted", + "In the middle", + "Somewhat extroverted", + "Very extroverted" + ] + }, ]; + +// List of valid countries (shortened for brevity, add more as needed) +const countryOptions = [ + "United States", "Canada", "United Kingdom", "Australia", "Germany", "France", "India", "China", "Japan", "Brazil", "Mexico", "Italy", "Spain", "Netherlands", "Sweden", "Norway", "Denmark", "Finland", "Ireland", "New Zealand" + // ...add more countries as needed +]; + + type FormValues = { - [key: string]: any; + [key: string]: any; }; + +// Helper to get visible questions based on current form values +const getVisibleQuestions = (values: FormValues) => + questions.filter((q) => !q.condition || q.condition(values)); + + const MultiStepForm: React.FC = () => { - const [step, setStep] = useState(0); - const {register, handleSubmit, getValues, formState: {errors}} = useForm(); - const isLastStep = step === questions.length - 1; - - const onSubmit: SubmitHandler = (data) => { - // handle final form submission - alert(JSON.stringify(data, null, 2)); - }; - - const nextStep = () => setStep((s) => Math.min(s + 1, questions.length - 1)); - const prevStep = () => setStep((s) => Math.max(s - 1, 0)); - - const currentQuestion = questions[step]; - - return ( -
-
- - {currentQuestion.type === "select" ? ( - - ) : ( - - )} - {errors[currentQuestion.name] && This field is required} -
-
- {step > 0 && ( - - )} - {!isLastStep ? ( - - ) : ( - - )} -
-
- ); + const [step, setStep] = useState(0); + const {register, handleSubmit, getValues, formState: {errors}} = useForm(); + const [formValues, setFormValues] = useState({}); + const [showGenderDefs, setShowGenderDefs] = useState(false); + + + // Helper to calculate age + const getAge = (month: string, day: string, year: string) => { + if (!month || !day || !year) return null; + const mm = parseInt(month, 10); + const dd = parseInt(day, 10); + const yyyy = parseInt(year, 10); + if (isNaN(mm) || isNaN(dd) || isNaN(yyyy)) return null; + const today = new Date(); + const birthDate = new Date(yyyy, mm - 1, dd); + let age = today.getFullYear() - yyyy; + const m = today.getMonth() - (mm - 1); + if (m < 0 || (m === 0 && today.getDate() < dd)) { + age--; + } + return age; + }; + + + const visibleQuestions = getVisibleQuestions(formValues); + const isLastStep = step === visibleQuestions.length - 1; + const currentQuestion = visibleQuestions[step]; + + + const onSubmit: SubmitHandler = (data) => { + // Merge the latest form values with the current step's data + const allValues = { ...formValues, ...getValues() }; + alert(JSON.stringify(allValues, null, 2)); + }; + + + const nextStep = () => { + const values = getValues(); + setFormValues((prev) => ({ ...prev, ...values })); + setStep((s) => Math.min(s + 1, visibleQuestions.length - 1)); + }; + const prevStep = () => setStep((s) => Math.max(s - 1, 0)); + + + // Questions where skip is allowed (from 'relationshipDuration' onward) + const skipFrom = visibleQuestions.findIndex(q => q.name === 'relationshipDuration'); + const canSkip = step >= skipFrom && skipFrom !== -1; + + + return ( +
+
+ + {currentQuestion.group && currentQuestion.fields ? ( + currentQuestion.fields.map((field) => { + let fieldInput; + if (field.name === "country") { + fieldInput = ( + <> + countryOptions.includes(value) || "Please select a valid country." + })} + defaultValue={getValues(field.name) || ""} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + nextStep(); + } + }} + className="text-center text-lg py-3 px-5 h-14 rounded-full border w-72" + /> + + {countryOptions.map((country) => ( + + + ); + } else if (field.name === "zipCode") { + fieldInput = ( + { + if (e.key === "Enter") { + e.preventDefault(); + nextStep(); + } + }} + className="text-center text-lg py-3 px-5 h-14 rounded-full border w-48" + /> + ); + } else { + fieldInput = ( + { + if (e.key === "Enter") { + e.preventDefault(); + nextStep(); + } + }} + className="text-center text-lg py-3 px-5 h-14 rounded-full border" + /> + ); + } + return ( +
+ + {fieldInput} + {errors[field.name] && ( + + {(errors[field.name] as any)?.message || "This field is required"} + + )} +
+ ); + }) + ) : currentQuestion.name === "birthday" ? ( +
+ + + + {(() => { + const month = getValues("birthday_month"); + const day = getValues("birthday_day"); + const year = getValues("birthday_year"); + const age = getAge(month, day, year); + if (age && age > 0 && age < 120) { + return You are {age}!; + } + return null; + })()} +
+ ) : (currentQuestion.type === "select" && currentQuestion.options && currentQuestion.options.length <= 6) ? ( +
+ {currentQuestion.options.map((opt) => ( + + ))} +
+ ) : (currentQuestion.type === "multiselect" && currentQuestion.options && currentQuestion.options.length <= 6) ? ( +
+ {currentQuestion.options.map((opt) => { + const selected = (formValues[currentQuestion.name] || []).includes(opt); + return ( + + ); + })} +
+ ) : currentQuestion.type === "select" ? ( + + ) : currentQuestion.type === "multiselect" ? ( + + // For a more advanced UI, consider using react-select's MultiSelect component + ) : currentQuestion.type === "textarea" ? ( +