CreateWorkOSDialog.tsx173 lines · main
1import { zodResolver } from '@hookform/resolvers/zod'
2import { useParams } from 'common'
3import { Trash } from 'lucide-react'
4import { useEffect } from 'react'
5import { SubmitHandler, useForm } from 'react-hook-form'
6import { toast } from 'sonner'
7import {
8 Button,
9 Dialog,
10 DialogContent,
11 DialogFooter,
12 DialogHeader,
13 DialogSection,
14 DialogTitle,
15 Form,
16 FormControl,
17 FormField,
18 Input,
19 Separator,
20} from 'ui'
21import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
22import * as z from 'zod'
23
24import { useCreateThirdPartyAuthIntegrationMutation } from '@/data/third-party-auth/integration-create-mutation'
25
26interface CreateWorkOSIntegrationProps {
27 visible: boolean
28 onClose: () => void
29 // TODO: Remove this if this Dialog is only used for creating.
30 onDelete: () => void
31}
32
33const FORM_ID = 'create-work-os-integration-form'
34
35const WORKOS_ISSUER =
36 /^https:\/\/(api.workos.com|[a-zA-Z0-9-]+([.][a-zA-Z0-9-]+){2,})\/(sso|user_management)\/(client|project)_[0-7][0-9A-HJKMNP-TV-Z]{25}\/?$/
37
38const FormSchema = z.object({
39 enabled: z.boolean(),
40 issuerURL: z
41 .string()
42 .trim()
43 .min(1)
44 .regex(
45 WORKOS_ISSUER,
46 'WorkOS URL contains invalid characters or does not have the correct structure.'
47 ),
48})
49
50export const CreateWorkOSIntegrationDialog = ({
51 visible,
52 onClose,
53 onDelete,
54}: CreateWorkOSIntegrationProps) => {
55 // TODO: Remove this if this Dialog is only used for creating.
56 const isCreating = true
57
58 const { ref: projectRef } = useParams()
59 const { mutate: createAuthIntegration, isPending } = useCreateThirdPartyAuthIntegrationMutation({
60 onSuccess: () => {
61 toast.success(`Successfully created a new WorkOS integration.`)
62 onClose()
63 },
64 })
65
66 const form = useForm<z.infer<typeof FormSchema>>({
67 resolver: zodResolver(FormSchema as any),
68 defaultValues: {
69 enabled: true,
70 issuerURL: '',
71 },
72 })
73
74 useEffect(() => {
75 if (visible) {
76 form.reset({
77 enabled: true,
78 issuerURL: '',
79 })
80 // the form input doesn't exist when the form is reset
81 setTimeout(() => {
82 form.setFocus('issuerURL')
83 }, 25)
84 }
85 }, [visible])
86
87 const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
88 createAuthIntegration({
89 projectRef: projectRef!,
90 oidcIssuerUrl: values.issuerURL,
91 })
92 }
93
94 return (
95 <Dialog open={visible} onOpenChange={() => onClose()}>
96 <DialogContent>
97 <DialogHeader>
98 <DialogTitle className="truncate">
99 {isCreating ? `Add new WorkOS connection` : `Update existing WorkOS connection`}
100 </DialogTitle>
101 </DialogHeader>
102
103 <Separator />
104 <DialogSection>
105 <Form {...form}>
106 <form id={FORM_ID} onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
107 {/* Enabled flag can't be changed for now because there's no update API call for integrations */}
108 {/* <FormField
109 key="enabled"
110 control={form.control}
111 name="enabled"
112 render={({ field }) => (
113 <FormItemLayout
114 className="px-8"
115 label={`Enable Firebase Auth Connection`}
116 layout="flex"
117 >
118 <FormControl>
119 <Switch
120 checked={field.value}
121 onCheckedChange={field.onChange}
122 disabled={field.disabled}
123 />
124 </FormControl>
125 </FormItemLayout>
126 )}
127 />
128 <Separator /> */}
129
130 <p className="text-sm text-foreground-light">
131 Enables a JWT from WorkOS to access data from this Briven project.
132 </p>
133 <FormField
134 key="issuerURL"
135 control={form.control}
136 name="issuerURL"
137 render={({ field }) => (
138 <FormItemLayout
139 label="WorkOS Issuer URL"
140 description="Obtain your issuer URL from the WorkOS dashboard."
141 >
142 <FormControl>
143 <Input
144 {...field}
145 placeholder="https://api.workos.com/user_management/client_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
146 />
147 </FormControl>
148 </FormItemLayout>
149 )}
150 />
151 </form>
152 </Form>
153 </DialogSection>
154 <DialogFooter>
155 {!isCreating && (
156 <div className="flex-1">
157 <Button type="danger" onClick={() => onDelete()} icon={<Trash />}>
158 Remove connection
159 </Button>
160 </div>
161 )}
162
163 <Button disabled={isPending} type="default" onClick={() => onClose()}>
164 Cancel
165 </Button>
166 <Button form={FORM_ID} htmlType="submit" disabled={isPending} loading={isPending}>
167 {isCreating ? 'Create connection' : 'Update connection'}
168 </Button>
169 </DialogFooter>
170 </DialogContent>
171 </Dialog>
172 )
173}