1
1
'use client' ;
2
2
3
3
import { CustomerField } from '@/app/lib/definitions' ;
4
+ import { useFormState } from 'react-dom' ;
4
5
import Link from 'next/link' ;
5
6
import {
6
7
CheckIcon ,
@@ -12,8 +13,11 @@ import { Button } from '@/app/ui/button';
12
13
import { createInvoice } from '@/app/lib/actions' ;
13
14
14
15
export default function Form ( { customers } : { customers : CustomerField [ ] } ) {
16
+ const initialState = { message : null , errors : { } } ;
17
+ const [ state , dispatch ] = useFormState ( createInvoice , initialState ) ;
18
+
15
19
return (
16
- < form action = { createInvoice } >
20
+ < form action = { dispatch } >
17
21
< div className = "rounded-md bg-gray-50 p-4 md:p-6" >
18
22
{ /* Customer Name */ }
19
23
< div className = "mb-4" >
@@ -26,6 +30,7 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
26
30
name = "customerId"
27
31
className = "peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
28
32
defaultValue = ""
33
+ aria-describedby = "customer-error"
29
34
>
30
35
< option value = "" disabled >
31
36
Select a customer
@@ -38,6 +43,14 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
38
43
</ select >
39
44
< UserCircleIcon className = "pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500" />
40
45
</ div >
46
+ < div id = "customer-error" aria-live = "polite" aria-atomic = "true" >
47
+ { state . errors ?. customerId &&
48
+ state . errors . customerId . map ( ( error : string ) => (
49
+ < p className = "mt-2 text-sm text-red-500" key = { error } >
50
+ { error }
51
+ </ p >
52
+ ) ) }
53
+ </ div >
41
54
</ div >
42
55
43
56
{ /* Invoice Amount */ }
@@ -53,10 +66,19 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
53
66
type = "number"
54
67
step = "0.01"
55
68
placeholder = "Enter USD amount"
69
+ aria-describedby = 'amount-error'
56
70
className = "peer block w-full rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
57
71
/>
58
72
< CurrencyDollarIcon className = "pointer-events-none absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
59
73
</ div >
74
+ < div id = "amount-error" aria-live = "polite" aria-atomic = "true" >
75
+ { state . errors ?. amount &&
76
+ state . errors . amount . map ( ( error : string ) => (
77
+ < p className = "mt-2 text-sm text-red-500" key = { error } >
78
+ { error }
79
+ </ p >
80
+ ) ) }
81
+ </ div >
60
82
</ div >
61
83
</ div >
62
84
@@ -73,6 +95,7 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
73
95
name = "status"
74
96
type = "radio"
75
97
value = "pending"
98
+ aria-describedby = 'status-error'
76
99
className = "h-4 w-4 cursor-pointer border-gray-300 bg-gray-100 text-gray-600 focus:ring-2"
77
100
/>
78
101
< label
@@ -97,6 +120,14 @@ export default function Form({ customers }: { customers: CustomerField[] }) {
97
120
Paid < CheckIcon className = "h-4 w-4" />
98
121
</ label >
99
122
</ div >
123
+ < div id = "status-error" aria-live = "polite" aria-atomic = "true" >
124
+ { state . errors ?. status &&
125
+ state . errors . status . map ( ( error : string ) => (
126
+ < p className = "mt-2 text-sm text-red-500" key = { error } >
127
+ { error }
128
+ </ p >
129
+ ) ) }
130
+ </ div >
100
131
</ div >
101
132
</ div >
102
133
</ fieldset >
0 commit comments