Skip to content

Commit b7186bc

Browse files
authored
secondary application form frontend No.51 (#55)
## Notion ticket link <!-- Please replace with your ticket's URL --> [Secondary Application Form](https://www.notion.so/uwblueprintexecs/Secondary-Application-Form-27210f3fb1dc80a7adcce3170891c182?source=copy_link) <!-- Give a quick summary of the implementation details, provide design justifications if necessary --> ## Implementation description * just the frontend according to the figma <!-- What should the reviewer do to verify your changes? Describe expected results and include screenshots when appropriate --> ## Steps to test 1. <!-- Draw attention to the substantial parts of your PR or anything you'd like a second opinion on --> ## What should reviewers focus on? * ## Checklist - [ ] My PR name is descriptive and in imperative tense - [ ] My commit messages are descriptive and in imperative tense. My commits are atomic and trivial commits are squashed or fixup'd into non-trivial commits - [ ] I have run the appropriate linter(s) - [ ] I have requested a review from the PL, as well as other devs who have background knowledge on this PR or who will be building on top of this PR
1 parent fc3bf48 commit b7186bc

File tree

4 files changed

+877
-7
lines changed

4 files changed

+877
-7
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import React, { useState } from 'react';
2+
import { Box, Heading, Text, Button, VStack, HStack, Textarea } from '@chakra-ui/react';
3+
import { useForm, Controller } from 'react-hook-form';
4+
import { FormField } from '@/components/ui/form-field';
5+
import { COLORS } from '@/constants/form';
6+
7+
interface VolunteerProfileFormData {
8+
experience: string;
9+
}
10+
11+
interface VolunteerProfileFormProps {
12+
onNext: (data: VolunteerProfileFormData) => void;
13+
onBack?: () => void;
14+
}
15+
16+
export function VolunteerProfileForm({ onNext, onBack }: VolunteerProfileFormProps) {
17+
const [wordCount, setWordCount] = useState(0);
18+
const maxWords = 300;
19+
20+
const {
21+
control,
22+
handleSubmit,
23+
watch,
24+
formState: { errors, isValid },
25+
} = useForm<VolunteerProfileFormData>({
26+
mode: 'onChange',
27+
defaultValues: {
28+
experience: '',
29+
},
30+
});
31+
32+
const experienceValue = watch('experience');
33+
34+
// Count words in real-time with more accurate word counting
35+
React.useEffect(() => {
36+
if (!experienceValue || experienceValue.trim() === '') {
37+
setWordCount(0);
38+
return;
39+
}
40+
41+
// More accurate word counting: split on whitespace and filter out empty strings
42+
const words = experienceValue
43+
.trim()
44+
.split(/\s+/)
45+
.filter((word) => word.length > 0 && word.match(/\S/));
46+
47+
setWordCount(words.length);
48+
}, [experienceValue]);
49+
50+
const onSubmit = (data: VolunteerProfileFormData) => {
51+
onNext(data);
52+
};
53+
54+
const isOverLimit = wordCount > maxWords;
55+
56+
return (
57+
<form onSubmit={handleSubmit(onSubmit)}>
58+
{/* Header */}
59+
<Heading
60+
as="h1"
61+
fontFamily="system-ui, -apple-system, sans-serif"
62+
fontWeight={600}
63+
color={COLORS.veniceBlue}
64+
fontSize="28px"
65+
mb={8}
66+
>
67+
Volunteer Profile Form
68+
</Heading>
69+
70+
{/* Progress Bar */}
71+
<Box mb={10}>
72+
<HStack gap={3}>
73+
<Box flex="1">
74+
<Box h="3px" bg={COLORS.teal} borderRadius="full" />
75+
</Box>
76+
<Box flex="1">
77+
<Box h="3px" bg={COLORS.progressGray} borderRadius="full" />
78+
</Box>
79+
</HStack>
80+
</Box>
81+
82+
{/* Your Experience Section */}
83+
<Box mb={10}>
84+
<Heading
85+
as="h2"
86+
fontFamily="system-ui, -apple-system, sans-serif"
87+
fontWeight={600}
88+
color={COLORS.veniceBlue}
89+
fontSize="20px"
90+
mb={3}
91+
>
92+
Your Experience
93+
</Heading>
94+
95+
<Text
96+
fontFamily="system-ui, -apple-system, sans-serif"
97+
fontSize="14px"
98+
color={COLORS.fieldGray}
99+
mb={6}
100+
>
101+
This information will serve as your biography and will encourage potential matches to
102+
speak with you.
103+
</Text>
104+
105+
<VStack gap={5} align="stretch">
106+
<Box>
107+
<Text
108+
fontFamily="system-ui, -apple-system, sans-serif"
109+
fontWeight={500}
110+
fontSize="14px"
111+
color={COLORS.veniceBlue}
112+
mb={2}
113+
>
114+
Tell us about yourself: include your age, diagnosis and treatments. Include personal
115+
details like if you're married or have kids, what you struggled with at diagnosis
116+
and/or treatment, and how you are doing now.
117+
</Text>
118+
119+
<Controller
120+
name="experience"
121+
control={control}
122+
rules={{
123+
required: 'Please tell us about your experience',
124+
validate: (value) => {
125+
if (!value || value.trim() === '') {
126+
return 'Please tell us about your experience';
127+
}
128+
129+
// Use the same accurate word counting logic as the display
130+
const words = value
131+
.trim()
132+
.split(/\s+/)
133+
.filter((word) => word.length > 0 && word.match(/\S/));
134+
135+
if (words.length > maxWords) {
136+
return `Please limit your response to ${maxWords} words`;
137+
}
138+
return true;
139+
},
140+
}}
141+
render={({ field }) => (
142+
<Box position="relative">
143+
<Textarea
144+
{...field}
145+
placeholder="Type here...."
146+
fontFamily="system-ui, -apple-system, sans-serif"
147+
fontSize="14px"
148+
color={COLORS.veniceBlue}
149+
borderColor={errors.experience ? 'red.500' : '#d1d5db'}
150+
borderRadius="6px"
151+
minH="200px"
152+
resize="vertical"
153+
border="1px solid"
154+
px={3}
155+
py={3}
156+
_placeholder={{ color: '#9ca3af' }}
157+
_focus={{
158+
borderColor: COLORS.teal,
159+
boxShadow: `0 0 0 3px ${COLORS.teal}20`,
160+
}}
161+
/>
162+
<Text
163+
position="absolute"
164+
bottom="8px"
165+
right="12px"
166+
fontSize="12px"
167+
color={isOverLimit ? 'red.500' : COLORS.fieldGray}
168+
fontFamily="system-ui, -apple-system, sans-serif"
169+
pointerEvents="none"
170+
>
171+
{wordCount}/{maxWords} words
172+
</Text>
173+
</Box>
174+
)}
175+
/>
176+
{errors.experience && (
177+
<Text color="red.500" fontSize="12px" mt={2}>
178+
{errors.experience.message}
179+
</Text>
180+
)}
181+
</Box>
182+
</VStack>
183+
</Box>
184+
185+
{/* Navigation Buttons */}
186+
<HStack justify="space-between" mt={8}>
187+
{onBack ? (
188+
<Button
189+
onClick={onBack}
190+
variant="outline"
191+
borderColor={COLORS.teal}
192+
color={COLORS.teal}
193+
fontFamily="system-ui, -apple-system, sans-serif"
194+
fontWeight={500}
195+
fontSize="14px"
196+
h="40px"
197+
px={6}
198+
_hover={{
199+
bg: `${COLORS.teal}10`,
200+
}}
201+
>
202+
Back
203+
</Button>
204+
) : (
205+
<Box />
206+
)}
207+
208+
<Button
209+
type="submit"
210+
bg={COLORS.teal}
211+
color="white"
212+
_hover={{ bg: COLORS.teal }}
213+
_active={{ bg: COLORS.teal }}
214+
disabled={!isValid || isOverLimit}
215+
w="auto"
216+
h="40px"
217+
fontSize="14px"
218+
fontWeight={500}
219+
px={6}
220+
>
221+
Next Section →
222+
</Button>
223+
</HStack>
224+
</form>
225+
);
226+
}

0 commit comments

Comments
 (0)