Skip to content

Commit 1033ac0

Browse files
committed
feat(challenge 62): implement cross-field validation in registration form using Signal Form
1 parent 543770b commit 1033ac0

2 files changed

Lines changed: 284 additions & 286 deletions

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
<div class="min-h-screen bg-gray-100 px-4 py-12 sm:px-6 lg:px-8">
2+
<div class="mx-auto max-w-md rounded-lg bg-white p-8 shadow-md">
3+
<h1 class="mb-6 text-3xl font-bold text-gray-900">Registration Form</h1>
4+
<p class="mb-6 text-sm text-gray-600">
5+
This form demonstrates cross field validation with reactive forms
6+
</p>
7+
8+
<form [formRoot]="form" class="space-y-6">
9+
<div>
10+
<label for="email" class="mb-2 block text-sm font-medium text-gray-700">
11+
Email
12+
<span class="text-red-500">*</span>
13+
</label>
14+
<input
15+
id="email"
16+
type="email"
17+
[formField]="form.email"
18+
placeholder="Enter your email"
19+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
20+
[class.border-red-500]="
21+
form.email().invalid() && form.email().touched()
22+
" />
23+
@if (form.email().invalid() && form.email().touched()) {
24+
@for (error of form.email().errors(); track error) {
25+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
26+
}
27+
}
28+
</div>
29+
30+
<div>
31+
<label
32+
for="password"
33+
class="mb-2 block text-sm font-medium text-gray-700">
34+
Password
35+
<span class="text-red-500">*</span>
36+
</label>
37+
<input
38+
id="password"
39+
type="password"
40+
[formField]="form.password"
41+
placeholder="Enter your password"
42+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
43+
[class.border-red-500]="
44+
form.password().invalid() && form.password().touched()
45+
" />
46+
@if (form.password().invalid() && form.password().touched()) {
47+
@for (error of form.password().errors(); track error) {
48+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
49+
}
50+
}
51+
</div>
52+
53+
<div>
54+
<label
55+
for="confirmPassword"
56+
class="mb-2 block text-sm font-medium text-gray-700">
57+
Confirm Password
58+
<span class="text-red-500">*</span>
59+
</label>
60+
<input
61+
id="confirmPassword"
62+
type="password"
63+
[formField]="form.confirmPassword"
64+
placeholder="Confirm your password"
65+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
66+
[class.border-red-500]="
67+
form.confirmPassword().invalid() && form.confirmPassword().touched()
68+
" />
69+
@if (
70+
form.confirmPassword().invalid() && form.confirmPassword().touched()
71+
) {
72+
@for (error of form.confirmPassword().errors(); track error) {
73+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
74+
}
75+
}
76+
</div>
77+
78+
<div>
79+
<label
80+
for="startDate"
81+
class="mb-2 block text-sm font-medium text-gray-700">
82+
Start Date
83+
<span class="text-red-500">*</span>
84+
</label>
85+
<input
86+
id="startDate"
87+
type="date"
88+
[formField]="form.startDate"
89+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
90+
[class.border-red-500]="
91+
form.startDate().invalid() && form.startDate().touched()
92+
" />
93+
@if (form.startDate().invalid() && form.startDate().touched()) {
94+
@for (error of form.startDate().errors(); track error) {
95+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
96+
}
97+
}
98+
</div>
99+
100+
<div>
101+
<label
102+
for="endDate"
103+
class="mb-2 block text-sm font-medium text-gray-700">
104+
End Date
105+
<span class="text-red-500">*</span>
106+
</label>
107+
<input
108+
id="endDate"
109+
type="date"
110+
[formField]="form.endDate"
111+
class="w-full rounded-md border border-gray-300 px-4 py-2 transition outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500"
112+
[class.border-red-500]="
113+
form.endDate().invalid() && form.endDate().touched()
114+
" />
115+
@if (form.endDate().invalid() && form.endDate().touched()) {
116+
@for (error of form.endDate().errors(); track error) {
117+
<p class="mt-1 text-sm text-red-600">{{ error.message }}</p>
118+
}
119+
}
120+
</div>
121+
122+
<div class="flex gap-4">
123+
<button
124+
type="submit"
125+
[disabled]="form().invalid()"
126+
class="flex-1 rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-400">
127+
Submit
128+
</button>
129+
<button
130+
type="button"
131+
(click)="onReset()"
132+
class="flex-1 rounded-md bg-gray-300 px-4 py-2 font-medium text-gray-700 transition hover:bg-gray-400">
133+
Reset
134+
</button>
135+
</div>
136+
</form>
137+
138+
<div class="mt-8 rounded-md bg-gray-50 p-4">
139+
<h2 class="mb-2 text-lg font-semibold text-gray-900">Form Status</h2>
140+
<div class="space-y-2 text-sm">
141+
<div class="flex justify-between">
142+
<span class="font-medium text-gray-700">Valid:</span>
143+
<span
144+
[class.text-green-600]="form().valid()"
145+
[class.text-red-600]="form().invalid()">
146+
{{ form().valid() ? 'Yes' : 'No' }}
147+
</span>
148+
</div>
149+
<div class="flex justify-between">
150+
<span class="font-medium text-gray-700">Touched:</span>
151+
<span>{{ form().touched() ? 'Yes' : 'No' }}</span>
152+
</div>
153+
<div class="flex justify-between">
154+
<span class="font-medium text-gray-700">Dirty:</span>
155+
<span>{{ form().dirty() ? 'Yes' : 'No' }}</span>
156+
</div>
157+
</div>
158+
<div class="mt-4">
159+
<h3 class="mb-2 font-medium text-gray-700">Form Value:</h3>
160+
<pre
161+
class="overflow-x-auto rounded bg-gray-800 p-3 text-xs text-gray-100"
162+
>{{ form().value() | json }}</pre
163+
>
164+
</div>
165+
</div>
166+
167+
@if (isSubmitted()) {
168+
<div class="mt-6 rounded-md border border-green-300 bg-green-50 p-4">
169+
<h2 class="mb-2 text-lg font-semibold text-green-900">
170+
Form Submitted Successfully!
171+
</h2>
172+
<pre
173+
class="overflow-x-auto rounded bg-green-800 p-3 text-xs text-green-100"
174+
>{{ form().value() | json }}</pre
175+
>
176+
</div>
177+
}
178+
</div>
179+
</div>

0 commit comments

Comments
 (0)