@@ -11,9 +11,12 @@ import {
11
11
FormMessage ,
12
12
} from "@/components/ui/form" ;
13
13
import { Input } from "@/components/ui/input" ;
14
+ import { Label } from "@/components/ui/label" ;
15
+ import { Switch } from "@/components/ui/switch" ;
14
16
import { cn } from "@/lib/utils" ;
15
17
import { zodResolver } from "@hookform/resolvers/zod" ;
16
- import { useForm } from "react-hook-form" ;
18
+ import { PlusIcon , Trash2Icon } from "lucide-react" ;
19
+ import { useFieldArray , useForm } from "react-hook-form" ;
17
20
import { toast } from "sonner" ;
18
21
import type { z } from "zod" ;
19
22
import type { Ecosystem , Partner } from "../../../../../types" ;
@@ -31,12 +34,22 @@ export function UpdatePartnerForm({
31
34
onSuccess : ( ) => void ;
32
35
authToken : string ;
33
36
} ) {
34
- const form = useForm < z . input < typeof partnerFormSchema > > ( {
37
+ // Check if partner has accessControl and serverVerifier
38
+ const hasAccessControl = ! ! partner . accessControl ;
39
+ const hasServerVerifier =
40
+ hasAccessControl && ! ! partner . accessControl ?. serverVerifier ;
41
+
42
+ const form = useForm < z . infer < typeof partnerFormSchema > > ( {
35
43
resolver : zodResolver ( partnerFormSchema ) ,
36
44
defaultValues : {
37
45
name : partner . name ,
38
46
domains : partner . allowlistedDomains . join ( "," ) ,
39
47
bundleIds : partner . allowlistedBundleIds . join ( "," ) ,
48
+ // Set the actual accessControl data if it exists
49
+ accessControl : partner . accessControl ,
50
+ // Set the UI control properties based on existing data
51
+ accessControlEnabled : hasAccessControl ,
52
+ serverVerifierEnabled : hasServerVerifier ,
40
53
} ,
41
54
} ) ;
42
55
@@ -59,10 +72,34 @@ export function UpdatePartnerForm({
59
72
} ,
60
73
) ;
61
74
75
+ // Watch the boolean flags for UI state
76
+ const accessControlEnabled = form . watch ( "accessControlEnabled" ) ;
77
+ const serverVerifierEnabled = form . watch ( "serverVerifierEnabled" ) ;
78
+
79
+ // Setup field array for headers
80
+ const customHeaderFields = useFieldArray ( {
81
+ control : form . control ,
82
+ name : "accessControl.serverVerifier.headers" ,
83
+ } ) ;
84
+
62
85
return (
63
86
< Form { ...form } >
64
87
< form
65
88
onSubmit = { form . handleSubmit ( ( values ) => {
89
+ // Construct the final accessControl object based on the enabled flags
90
+ let finalAccessControl : Partner [ "accessControl" ] | null = null ;
91
+
92
+ if ( values . accessControlEnabled ) {
93
+ finalAccessControl = { } as Partner [ "accessControl" ] ;
94
+
95
+ if ( finalAccessControl && values . serverVerifierEnabled ) {
96
+ finalAccessControl . serverVerifier = {
97
+ url : values . accessControl ?. serverVerifier ?. url || "" ,
98
+ headers : values . accessControl ?. serverVerifier ?. headers || [ ] ,
99
+ } ;
100
+ }
101
+ }
102
+
66
103
updatePartner ( {
67
104
ecosystem,
68
105
partnerId : partner . id ,
@@ -73,6 +110,7 @@ export function UpdatePartnerForm({
73
110
allowlistedBundleIds : values . bundleIds
74
111
. split ( / , | / )
75
112
. filter ( ( d ) => d . length > 0 ) ,
113
+ accessControl : finalAccessControl ,
76
114
} ) ;
77
115
} ) }
78
116
className = "flex flex-col gap-4"
@@ -81,7 +119,7 @@ export function UpdatePartnerForm({
81
119
< FormField
82
120
control = { form . control }
83
121
name = "name"
84
- defaultValue = "" // Note: you *must* provide a default value here or the field won't reset
122
+ defaultValue = ""
85
123
render = { ( { field } ) => (
86
124
< FormItem className = "col-span-4" >
87
125
< FormLabel > Name </ FormLabel >
@@ -108,7 +146,7 @@ export function UpdatePartnerForm({
108
146
< FormField
109
147
control = { form . control }
110
148
name = "domains"
111
- defaultValue = "" // Note: you *must* provide a default value here or the field won't reset
149
+ defaultValue = ""
112
150
render = { ( { field } ) => (
113
151
< FormItem className = "col-span-4 lg:col-span-4" >
114
152
< FormLabel > Domains </ FormLabel >
@@ -119,7 +157,7 @@ export function UpdatePartnerForm({
119
157
className = { cn (
120
158
"block text-xs" ,
121
159
form . formState . errors . domains ?. message &&
122
- "block translate-y-0 text-destructive opacity-100" , // If there are errors show them rather than the tip
160
+ "block translate-y-0 text-destructive opacity-100" ,
123
161
) }
124
162
>
125
163
{ form . formState . errors . domains ?. message ??
@@ -131,7 +169,7 @@ export function UpdatePartnerForm({
131
169
< FormField
132
170
control = { form . control }
133
171
name = "bundleIds"
134
- defaultValue = "" // Note: you *must* provide a default value here or the field won't reset
172
+ defaultValue = ""
135
173
render = { ( { field } ) => (
136
174
< FormItem className = "col-span-4" >
137
175
< FormLabel > Bundle ID </ FormLabel >
@@ -152,6 +190,144 @@ export function UpdatePartnerForm({
152
190
</ FormItem >
153
191
) }
154
192
/>
193
+
194
+ { /* Access Control Section */ }
195
+ < div className = "mb-4 flex items-center justify-between gap-6" >
196
+ < div >
197
+ < Label htmlFor = "access-control-switch" className = "text-base" >
198
+ Access Control
199
+ </ Label >
200
+ < p className = "mt-0.5 text-muted-foreground text-xs" >
201
+ Enable access control for this partner
202
+ </ p >
203
+ </ div >
204
+ < Switch
205
+ id = "access-control-switch"
206
+ checked = { accessControlEnabled }
207
+ onCheckedChange = { ( checked ) => {
208
+ form . setValue ( "accessControlEnabled" , checked ) ;
209
+ // If disabling access control, also disable server verifier
210
+ if ( ! checked ) {
211
+ form . setValue ( "serverVerifierEnabled" , false ) ;
212
+ }
213
+ } }
214
+ />
215
+ </ div >
216
+
217
+ { accessControlEnabled && (
218
+ < div className = "rounded-lg border border-border p-4" >
219
+ < div className = "mb-4 flex items-center justify-between gap-6" >
220
+ < div >
221
+ < Label htmlFor = "server-verifier-switch" className = "text-base" >
222
+ Server Verifier
223
+ </ Label >
224
+ < p className = "mt-0.5 text-muted-foreground text-xs" >
225
+ Configure a server verifier for access control
226
+ </ p >
227
+ </ div >
228
+ < Switch
229
+ id = "server-verifier-switch"
230
+ checked = { serverVerifierEnabled }
231
+ onCheckedChange = { ( checked ) => {
232
+ form . setValue ( "serverVerifierEnabled" , checked ) ;
233
+
234
+ // Initialize serverVerifier fields if enabling
235
+ if (
236
+ checked &&
237
+ ! form . getValues ( "accessControl.serverVerifier" )
238
+ ) {
239
+ form . setValue ( "accessControl.serverVerifier" , {
240
+ url : "" ,
241
+ headers : [ ] ,
242
+ } ) ;
243
+ }
244
+ } }
245
+ />
246
+ </ div >
247
+
248
+ { serverVerifierEnabled && (
249
+ < div className = "mt-4 grid grid-cols-1 gap-6" >
250
+ < FormField
251
+ control = { form . control }
252
+ name = "accessControl.serverVerifier.url"
253
+ render = { ( { field } ) => (
254
+ < FormItem >
255
+ < FormLabel > Server Verifier URL</ FormLabel >
256
+ < FormControl >
257
+ < Input
258
+ { ...field }
259
+ placeholder = "https://example.com/your-verifier"
260
+ />
261
+ </ FormControl >
262
+ < FormDescription className = "text-xs" >
263
+ Enter the URL of your server where verification
264
+ requests will be sent
265
+ </ FormDescription >
266
+ < FormMessage />
267
+ </ FormItem >
268
+ ) }
269
+ />
270
+
271
+ < div >
272
+ < Label className = "mb-3 inline-block" > Custom Headers</ Label >
273
+ < div className = "flex flex-col gap-4" >
274
+ { customHeaderFields . fields . map ( ( field , headerIdx ) => {
275
+ return (
276
+ < div className = "flex gap-4" key = { field . id } >
277
+ < Input
278
+ placeholder = "Name"
279
+ type = "text"
280
+ { ...form . register (
281
+ `accessControl.serverVerifier.headers.${ headerIdx } .key` ,
282
+ ) }
283
+ />
284
+ < Input
285
+ placeholder = "Value"
286
+ type = "text"
287
+ { ...form . register (
288
+ `accessControl.serverVerifier.headers.${ headerIdx } .value` ,
289
+ ) }
290
+ />
291
+ < Button
292
+ variant = "outline"
293
+ aria-label = "Remove header"
294
+ onClick = { ( ) => {
295
+ customHeaderFields . remove ( headerIdx ) ;
296
+ } }
297
+ className = "!w-auto px-3"
298
+ type = "button"
299
+ >
300
+ < Trash2Icon className = "size-4 shrink-0 text-destructive-text" />
301
+ </ Button >
302
+ </ div >
303
+ ) ;
304
+ } ) }
305
+
306
+ < Button
307
+ variant = "outline"
308
+ className = "w-full gap-2 bg-background"
309
+ onClick = { ( ) => {
310
+ customHeaderFields . append ( {
311
+ key : "" ,
312
+ value : "" ,
313
+ } ) ;
314
+ } }
315
+ type = "button"
316
+ >
317
+ < PlusIcon className = "size-4" />
318
+ Add header
319
+ </ Button >
320
+ </ div >
321
+
322
+ < p className = "mt-3 text-muted-foreground text-xs" >
323
+ Set custom headers to be sent along with verification
324
+ requests
325
+ </ p >
326
+ </ div >
327
+ </ div >
328
+ ) }
329
+ </ div >
330
+ ) }
155
331
</ div >
156
332
157
333
< Button
0 commit comments