This repository was archived by the owner on Mar 24, 2026. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathFormWrapper.tsx
More file actions
190 lines (172 loc) · 5.65 KB
/
FormWrapper.tsx
File metadata and controls
190 lines (172 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
'use client';
import type { UseFormReturn } from 'react-hook-form';
import { Form } from '@/components/ui/form';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { ReloadIcon } from '@radix-ui/react-icons';
import InputField from './InputField';
import TextareaField from './TextareaField';
import SelectField from './SelectField';
import NumberField from './NumberField';
import DigitsOnlyField from './DigitsOnlyField';
import type { FormQuestion } from '@/types';
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
interface FormWrapperProps {
form?: UseFormReturn<Record<string, unknown>>; // Replace any with more specific type
questions: FormQuestion[];
onSubmit: (values: Record<string, unknown>) => Promise<void>;
title: string;
description?: string;
submitText?: string;
isSubmitting?: boolean;
error?: string;
className?: string;
hideSubmitButton?: boolean;
}
// Helper function to check if a question should be shown based on dependencies
const shouldShowQuestion = (
question: FormQuestion,
formValues: Record<string, unknown>
): boolean => {
// If the question has no showIf condition, always show it
if (!question.showIf) return true;
// Check each condition to determine if the question should be shown
for (const [dependentField, requiredValue] of Object.entries(
question.showIf
)) {
const fieldValue = formValues[dependentField];
// If required value is an array, check if the current value is in that array
if (Array.isArray(requiredValue)) {
// Convert to string for comparison since form values are often strings
const strValue =
typeof fieldValue === 'string' ? fieldValue : String(fieldValue || '');
if (!requiredValue.includes(strValue)) {
return false;
}
}
// Otherwise check if the current value equals the required value
else if (fieldValue !== requiredValue) {
return false;
}
}
return true;
};
export default function FormWrapper({
form: formProp,
questions,
onSubmit,
title,
description,
submitText = 'Submit Application',
isSubmitting = false,
error,
className = 'space-y-6 max-w-2xl mx-auto p-4',
hideSubmitButton = false,
}: FormWrapperProps) {
// Try to get form from context, otherwise use the provided form prop
const formContext = useFormContext();
const form = formProp || formContext;
// Filter questions to only show ones that satisfy their conditions
const visibleQuestions = useMemo(() => {
// Move formValues inside useMemo to fix exhaustive deps warning
const formValues = form?.watch() || {};
return questions.filter((q) => shouldShowQuestion(q, formValues));
}, [form, questions]);
if (!form) {
console.error('Form is required. Provide it as a prop or via FormProvider');
return null;
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className={className}>
<div className="space-y-2">
<h2 className="text-2xl font-semibold">{title}</h2>
{description && (
<p className="text-muted-foreground">{description}</p>
)}
</div>
{/* Only show error alert if explicitly provided and not empty */}
{error && error.length > 0 && (
<Alert variant="destructive" className="mt-4">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div className="space-y-8">
{visibleQuestions.map((q) => {
const commonProps = {
name: q.name,
label: q.question,
required: !q.optional,
description: q.description,
disabled: isSubmitting,
};
switch (q.type) {
case 'short':
return (
<InputField
key={q.name}
{...commonProps}
type={q.inputType || 'text'}
/>
);
case 'paragraph':
return (
<TextareaField
key={q.name}
{...commonProps}
rows={q.rows || 4}
/>
);
case 'select':
return (
<SelectField
key={q.name}
{...commonProps}
options={q.options || []}
/>
);
case 'number':
return (
<NumberField
key={q.name}
{...commonProps}
min={q.min}
max={q.max}
step={q.step}
/>
);
case 'digits-only':
return (
<DigitsOnlyField
key={q.name}
{...commonProps}
minLength={q.minLength}
maxLength={q.maxLength}
placeholder={q.placeholder}
/>
);
default:
return null;
}
})}
</div>
{!hideSubmitButton && (
<div className="mt-8 flex justify-end">
<Button
type="submit"
size="lg"
disabled={isSubmitting}
className="w-full md:w-auto min-w-[200px]"
>
{isSubmitting && (
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
)}
{submitText}
</Button>
</div>
)}
</form>
</Form>
);
}