Skip to content

Commit 14b04db

Browse files
committed
feat(challenge 63): convert checkout form from reactive form to signal form
1 parent 543770b commit 14b04db

8 files changed

Lines changed: 242 additions & 273 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<div class="grid gap-4 sm:grid-cols-2" data-testid="billing-fields">
2+
<label
3+
class="flex flex-col gap-1 text-sm font-medium text-slate-700 sm:col-span-2">
4+
Street
5+
<input
6+
class="input"
7+
type="text"
8+
[formField]="fieldTree().street"
9+
aria-required="true" />
10+
<app-validation-message [fieldState]="fieldTree().street()" />
11+
</label>
12+
<label class="flex flex-col gap-1 text-sm font-medium text-slate-700">
13+
ZIP code
14+
<input
15+
class="input"
16+
type="text"
17+
[formField]="fieldTree().zipcode"
18+
aria-required="true" />
19+
<app-validation-message [fieldState]="fieldTree().zipcode()" />
20+
</label>
21+
<label class="flex flex-col gap-1 text-sm font-medium text-slate-700">
22+
City
23+
<input
24+
class="input"
25+
type="text"
26+
[formField]="fieldTree().city"
27+
aria-required="true" />
28+
<app-validation-message [fieldState]="fieldTree().city()" />
29+
</label>
30+
</div>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@reference "tailwindcss";
2+
3+
.input {
4+
@apply w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200;
5+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Component, input } from '@angular/core';
2+
import { FieldTree, FormField } from '@angular/forms/signals';
3+
import { AddressModel } from '../checkout-form.model';
4+
import { ValidationMessageComponent } from '../validation-message/validation-message.component';
5+
6+
@Component({
7+
selector: 'app-address-form',
8+
templateUrl: 'address-form.component.html',
9+
styleUrls: ['address-form.component.scss'],
10+
imports: [FormField, ValidationMessageComponent],
11+
})
12+
export class AddressFormComponent {
13+
fieldTree = input.required<FieldTree<AddressModel>>();
14+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<main class="min-h-screen bg-slate-50 text-slate-900">
2+
<div class="mx-auto max-w-4xl px-6 py-12">
3+
<h1 class="mb-6 text-3xl font-semibold">Order</h1>
4+
<form
5+
[formRoot]="checkoutForm"
6+
class="space-y-8 rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
7+
<section class="space-y-4">
8+
<h2 class="text-xl font-semibold">Information</h2>
9+
<div class="grid gap-4 sm:grid-cols-2">
10+
<label class="flex flex-col gap-1 text-sm font-medium text-slate-700">
11+
Last name
12+
<input
13+
class="input"
14+
type="text"
15+
[formField]="checkoutForm.lastName"
16+
aria-required="true" />
17+
<app-validation-message [fieldState]="checkoutForm.lastName()" />
18+
</label>
19+
<label class="flex flex-col gap-1 text-sm font-medium text-slate-700">
20+
First name
21+
<input
22+
class="input"
23+
type="text"
24+
[formField]="checkoutForm.firstName"
25+
aria-required="true" />
26+
<app-validation-message [fieldState]="checkoutForm.firstName()" />
27+
</label>
28+
</div>
29+
</section>
30+
31+
<section class="space-y-4">
32+
<h2 class="text-xl font-semibold">Shipping address</h2>
33+
<app-address-form [fieldTree]="checkoutForm.shipping" />
34+
</section>
35+
36+
<section class="space-y-4">
37+
<div class="flex items-center justify-between gap-4">
38+
<h2 class="text-xl font-semibold">Billing address</h2>
39+
<label
40+
class="flex items-center gap-2 text-sm font-medium text-slate-700">
41+
<input
42+
type="checkbox"
43+
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
44+
[formField]="checkoutForm.sameAsShipping" />
45+
Billing address same as shipping
46+
</label>
47+
</div>
48+
49+
@if (!checkoutForm.billing().hidden()) {
50+
<app-address-form [fieldTree]="checkoutForm.billing" />
51+
}
52+
</section>
53+
54+
<div
55+
class="flex items-center justify-between border-t border-slate-200 pt-4">
56+
<div class="text-sm text-slate-600">
57+
<span [class.text-rose-600]="checkoutForm().invalid()">
58+
{{
59+
checkoutForm().invalid() ? 'Form incomplete' : 'Ready to submit'
60+
}}
61+
</span>
62+
</div>
63+
<button
64+
type="submit"
65+
class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-500 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:bg-slate-300">
66+
Submit
67+
</button>
68+
</div>
69+
</form>
70+
71+
<section
72+
class="mt-6 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
73+
<h3 class="mb-2 text-lg font-semibold">Preview</h3>
74+
<pre
75+
class="overflow-x-auto rounded bg-slate-900 p-4 text-sm text-slate-100"
76+
>{{ checkoutForm().value() | json }}</pre
77+
>
78+
</section>
79+
</div>
80+
</main>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@reference "tailwindcss";
2+
3+
.input {
4+
@apply w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm shadow-sm transition outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200;
5+
}

0 commit comments

Comments
 (0)