Skip to content

Commit 96e4f8b

Browse files
committed
feat(form): add blur event validation feedback
Resolves #262
1 parent 144b403 commit 96e4f8b

1 file changed

Lines changed: 28 additions & 12 deletions

File tree

frontend/src/components/CreateCampaignForm.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function CreateCampaignForm({
3232
const [validationErrors, setValidationErrors] = useState<FormErrors>(
3333
validateForm({ ...INITIAL_VALUES, acceptedTokens: assetOptions.slice(0, 1) }),
3434
);
35+
const [touchedFields, setTouchedFields] = useState<Set<string>>(new Set());
3536
const [isSubmitting, setIsSubmitting] = useState(false);
3637

3738
useEffect(() => {
@@ -51,7 +52,16 @@ export function CreateCampaignForm({
5152
function update(field: keyof typeof INITIAL_VALUES, value: any) {
5253
const nextValues = { ...values, [field]: value };
5354
setValues(nextValues);
54-
setValidationErrors(validateForm(nextValues));
55+
if (touchedFields.has(field)) {
56+
setValidationErrors(validateForm(nextValues));
57+
}
58+
}
59+
60+
function handleFieldBlur(field: string) {
61+
const newTouched = new Set(touchedFields);
62+
newTouched.add(field);
63+
setTouchedFields(newTouched);
64+
setValidationErrors(validateForm(values));
5565
}
5666

5767
function toggleToken(token: string) {
@@ -115,11 +125,12 @@ export function CreateCampaignForm({
115125
type="text"
116126
value={values.creator}
117127
onChange={(event) => update('creator', event.target.value)}
128+
onBlur={() => handleFieldBlur('creator')}
118129
placeholder="G... creator public key"
119-
className={validationErrors.creator ? 'input-error' : ''}
130+
className={validationErrors.creator && touchedFields.has('creator') ? 'input-error' : ''}
120131
required
121132
/>
122-
{validationErrors.creator ? (
133+
{validationErrors.creator && touchedFields.has('creator') ? (
123134
<span className="field-error">{validationErrors.creator}</span>
124135
) : null}
125136
</label>
@@ -130,13 +141,14 @@ export function CreateCampaignForm({
130141
type="text"
131142
value={values.title}
132143
onChange={(event) => update('title', event.target.value)}
144+
onBlur={() => handleFieldBlur('title')}
133145
placeholder="Stellar community design sprint"
134146
minLength={4}
135147
maxLength={80}
136-
className={validationErrors.title ? 'input-error' : ''}
148+
className={validationErrors.title && touchedFields.has('title') ? 'input-error' : ''}
137149
required
138150
/>
139-
{validationErrors.title ? (
151+
{validationErrors.title && touchedFields.has('title') ? (
140152
<span className="field-error">{validationErrors.title}</span>
141153
) : null}
142154
</label>
@@ -146,14 +158,15 @@ export function CreateCampaignForm({
146158
<textarea
147159
value={values.description}
148160
onChange={(event) => update('description', event.target.value)}
161+
onBlur={() => handleFieldBlur('description')}
149162
placeholder="Describe what the campaign funds, who benefits, and the delivery plan."
150163
rows={5}
151164
minLength={20}
152165
maxLength={500}
153-
className={validationErrors.description ? 'input-error' : ''}
166+
className={validationErrors.description && touchedFields.has('description') ? 'input-error' : ''}
154167
required
155168
/>
156-
{validationErrors.description ? (
169+
{validationErrors.description && touchedFields.has('description') ? (
157170
<span className="field-error">{validationErrors.description}</span>
158171
) : null}
159172
</label>
@@ -167,12 +180,13 @@ export function CreateCampaignForm({
167180
type="checkbox"
168181
checked={values.acceptedTokens.includes(asset)}
169182
onChange={() => toggleToken(asset)}
183+
onBlur={() => handleFieldBlur('acceptedTokens')}
170184
/>
171185
{asset}
172186
</label>
173187
))}
174188
</div>
175-
{validationErrors.acceptedTokens ? (
189+
{validationErrors.acceptedTokens && touchedFields.has('acceptedTokens') ? (
176190
<span className="field-error">{validationErrors.acceptedTokens}</span>
177191
) : null}
178192
</div>
@@ -185,10 +199,11 @@ export function CreateCampaignForm({
185199
step="0.01"
186200
value={values.targetAmount}
187201
onChange={(event) => update('targetAmount', event.target.value)}
188-
className={validationErrors.targetAmount ? 'input-error' : ''}
202+
onBlur={() => handleFieldBlur('targetAmount')}
203+
className={validationErrors.targetAmount && touchedFields.has('targetAmount') ? 'input-error' : ''}
189204
required
190205
/>
191-
{validationErrors.targetAmount ? (
206+
{validationErrors.targetAmount && touchedFields.has('targetAmount') ? (
192207
<span className="field-error">{validationErrors.targetAmount}</span>
193208
) : null}
194209
</label>
@@ -201,10 +216,11 @@ export function CreateCampaignForm({
201216
step="0.0001"
202217
value={values.deadlineHours}
203218
onChange={(event) => update('deadlineHours', event.target.value)}
204-
className={validationErrors.deadlineHours ? 'input-error' : ''}
219+
onBlur={() => handleFieldBlur('deadlineHours')}
220+
className={validationErrors.deadlineHours && touchedFields.has('deadlineHours') ? 'input-error' : ''}
205221
required
206222
/>
207-
{validationErrors.deadlineHours ? (
223+
{validationErrors.deadlineHours && touchedFields.has('deadlineHours') ? (
208224
<span className="field-error">{validationErrors.deadlineHours}</span>
209225
) : null}
210226
</label>

0 commit comments

Comments
 (0)