CreateClerkAuthDialog.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import { zodResolver } from '@hookform/resolvers/zod'
  2. import { useParams } from 'common'
  3. import { useEffect } from 'react'
  4. import { SubmitHandler, useForm } from 'react-hook-form'
  5. import { toast } from 'sonner'
  6. import {
  7. Button,
  8. Dialog,
  9. DialogContent,
  10. DialogFooter,
  11. DialogHeader,
  12. DialogSection,
  13. DialogTitle,
  14. Form,
  15. FormControl,
  16. FormField,
  17. Input,
  18. Separator,
  19. } from 'ui'
  20. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  21. import * as z from 'zod'
  22. import { InlineLink } from '@/components/ui/InlineLink'
  23. import { useCreateThirdPartyAuthIntegrationMutation } from '@/data/third-party-auth/integration-create-mutation'
  24. interface CreateClerkAuthIntegrationProps {
  25. visible: boolean
  26. prod?: boolean
  27. onClose: () => void
  28. // TODO: Remove this if this Dialog is only used for creating.
  29. onDelete: () => void
  30. }
  31. const FORM_ID = 'create-firebase-auth-integration-form'
  32. const FormSchema = z
  33. .object({
  34. enabled: z.boolean(),
  35. domain: z.string(),
  36. })
  37. .superRefine((val, ctx) => {
  38. if (
  39. !val.domain.match(/https:\/\/clerk([.][a-z0-9-]+){2,}\/?/) &&
  40. !val.domain.match(/https:\/\/[a-z0-9-]+[.]clerk[.]accounts[.]dev\/?$/)
  41. ) {
  42. ctx.addIssue({
  43. code: z.ZodIssueCode.invalid_string,
  44. path: ['domain'],
  45. message:
  46. 'Production Clerk domains use HTTPS and start with the clerk subdomain (https://clerk.example.com). Development Clerk domains use HTTPS and end with .clerk.accounts.dev (https://example.clerk.accounts.dev).',
  47. validation: 'regex',
  48. })
  49. }
  50. })
  51. export const CreateClerkAuthIntegrationDialog = ({
  52. visible,
  53. onClose,
  54. }: CreateClerkAuthIntegrationProps) => {
  55. const { ref: projectRef } = useParams()
  56. const { mutate: createAuthIntegration, isPending } = useCreateThirdPartyAuthIntegrationMutation({
  57. onSuccess: () => {
  58. toast.success(`Successfully created a new Clerk integration.`)
  59. onClose()
  60. },
  61. })
  62. const form = useForm<z.infer<typeof FormSchema>>({
  63. resolver: zodResolver(FormSchema as any),
  64. defaultValues: {
  65. enabled: true,
  66. domain: '',
  67. },
  68. })
  69. useEffect(() => {
  70. if (visible) {
  71. form.reset({
  72. enabled: true,
  73. domain: '',
  74. })
  75. // the form input doesn't exist when the form is reset
  76. setTimeout(() => {
  77. form.setFocus('domain')
  78. }, 25)
  79. }
  80. }, [visible])
  81. const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
  82. createAuthIntegration({
  83. projectRef: projectRef!,
  84. oidcIssuerUrl: values.domain,
  85. })
  86. }
  87. return (
  88. <Dialog open={visible} onOpenChange={() => onClose()}>
  89. <DialogContent>
  90. <DialogHeader>
  91. <DialogTitle className="truncate">Add new Clerk connection</DialogTitle>
  92. </DialogHeader>
  93. <Separator />
  94. <DialogSection>
  95. <Form {...form}>
  96. <form id={FORM_ID} onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
  97. <p className="text-sm text-foreground-light">
  98. Register your Clerk domain. Visit{' '}
  99. <InlineLink
  100. href="https://dashboard.clerk.com/setup/briven"
  101. target="_blank"
  102. rel="noopener"
  103. >
  104. Clerk's Connect with Briven page
  105. </InlineLink>{' '}
  106. to configure your Clerk instance.
  107. </p>
  108. <FormField
  109. key="domain"
  110. control={form.control}
  111. name="domain"
  112. render={({ field }) => (
  113. <FormItemLayout label="Clerk Domain">
  114. <FormControl>
  115. <Input
  116. {...field}
  117. placeholder={
  118. 'https://clerk.example.com or https://example.clerk.accounts.dev'
  119. }
  120. />
  121. </FormControl>
  122. </FormItemLayout>
  123. )}
  124. />
  125. </form>
  126. </Form>
  127. </DialogSection>
  128. <DialogFooter>
  129. <Button disabled={isPending} type="default" onClick={() => onClose()}>
  130. Cancel
  131. </Button>
  132. <Button form={FORM_ID} htmlType="submit" disabled={isPending} loading={isPending}>
  133. Create connection
  134. </Button>
  135. </DialogFooter>
  136. </DialogContent>
  137. </Dialog>
  138. )
  139. }