Skip to content

Commit e60b88b

Browse files
[Dashboard] feat: Add access control and server verifier to partner configuration
1 parent 269fbe5 commit e60b88b

File tree

4 files changed

+214
-6
lines changed

4 files changed

+214
-6
lines changed

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/update-partner-form.client.tsx

Lines changed: 182 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ import {
1111
FormMessage,
1212
} from "@/components/ui/form";
1313
import { Input } from "@/components/ui/input";
14+
import { Label } from "@/components/ui/label";
15+
import { Switch } from "@/components/ui/switch";
1416
import { cn } from "@/lib/utils";
1517
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";
1720
import { toast } from "sonner";
1821
import type { z } from "zod";
1922
import type { Ecosystem, Partner } from "../../../../../types";
@@ -31,12 +34,22 @@ export function UpdatePartnerForm({
3134
onSuccess: () => void;
3235
authToken: string;
3336
}) {
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>>({
3543
resolver: zodResolver(partnerFormSchema),
3644
defaultValues: {
3745
name: partner.name,
3846
domains: partner.allowlistedDomains.join(","),
3947
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,
4053
},
4154
});
4255

@@ -59,10 +72,34 @@ export function UpdatePartnerForm({
5972
},
6073
);
6174

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+
6285
return (
6386
<Form {...form}>
6487
<form
6588
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+
66103
updatePartner({
67104
ecosystem,
68105
partnerId: partner.id,
@@ -73,6 +110,7 @@ export function UpdatePartnerForm({
73110
allowlistedBundleIds: values.bundleIds
74111
.split(/,| /)
75112
.filter((d) => d.length > 0),
113+
accessControl: finalAccessControl,
76114
});
77115
})}
78116
className="flex flex-col gap-4"
@@ -81,7 +119,7 @@ export function UpdatePartnerForm({
81119
<FormField
82120
control={form.control}
83121
name="name"
84-
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
122+
defaultValue=""
85123
render={({ field }) => (
86124
<FormItem className="col-span-4">
87125
<FormLabel> Name </FormLabel>
@@ -108,7 +146,7 @@ export function UpdatePartnerForm({
108146
<FormField
109147
control={form.control}
110148
name="domains"
111-
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
149+
defaultValue=""
112150
render={({ field }) => (
113151
<FormItem className="col-span-4 lg:col-span-4">
114152
<FormLabel> Domains </FormLabel>
@@ -119,7 +157,7 @@ export function UpdatePartnerForm({
119157
className={cn(
120158
"block text-xs",
121159
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",
123161
)}
124162
>
125163
{form.formState.errors.domains?.message ??
@@ -131,7 +169,7 @@ export function UpdatePartnerForm({
131169
<FormField
132170
control={form.control}
133171
name="bundleIds"
134-
defaultValue="" // Note: you *must* provide a default value here or the field won't reset
172+
defaultValue=""
135173
render={({ field }) => (
136174
<FormItem className="col-span-4">
137175
<FormLabel> Bundle ID </FormLabel>
@@ -152,6 +190,144 @@ export function UpdatePartnerForm({
152190
</FormItem>
153191
)}
154192
/>
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+
)}
155331
</div>
156332

157333
<Button

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/constants.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,23 @@ export const partnerFormSchema = z.object({
3030
.filter((d) => d.length > 0)
3131
.join(","),
3232
),
33+
accessControlEnabled: z.boolean(),
34+
serverVerifierEnabled: z.boolean(),
35+
accessControl: z
36+
.object({
37+
serverVerifier: z
38+
.object({
39+
url: z.string().url({ message: "Please enter a valid URL" }),
40+
headers: z
41+
.array(
42+
z.object({
43+
key: z.string(),
44+
value: z.string(),
45+
}),
46+
)
47+
.optional(),
48+
})
49+
.optional(),
50+
})
51+
.optional(),
3352
});

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/hooks/use-update-partner.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ type UpdatePartnerParams = {
1111
name: string;
1212
allowlistedDomains: string[];
1313
allowlistedBundleIds: string[];
14+
accessControl?: {
15+
serverVerifier?: {
16+
url: string;
17+
headers?: { key: string; value: string }[];
18+
} | null;
19+
} | null;
1420
};
1521

1622
export function useUpdatePartner(
@@ -42,6 +48,7 @@ export function useUpdatePartner(
4248
name: params.name,
4349
allowlistedDomains: params.allowlistedDomains,
4450
allowlistedBundleIds: params.allowlistedBundleIds,
51+
accessControl: params.accessControl,
4552
}),
4653
},
4754
);

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,10 @@ export type Partner = {
5555
permissions: [PartnerPermission];
5656
createdAt: string;
5757
updatedAt: string;
58+
accessControl?: {
59+
serverVerifier?: {
60+
url: string;
61+
headers?: { key: string; value: string }[];
62+
};
63+
};
5864
};

0 commit comments

Comments
 (0)