AWSPrivateLinkForm.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import Link from 'next/link'
  2. import { useEffect } from 'react'
  3. import { useForm } from 'react-hook-form'
  4. import { toast } from 'sonner'
  5. import {
  6. Badge,
  7. Button,
  8. Form,
  9. FormControl,
  10. FormField,
  11. Input,
  12. Sheet,
  13. SheetContent,
  14. SheetDescription,
  15. SheetFooter,
  16. SheetHeader,
  17. SheetSection,
  18. SheetTitle,
  19. } from 'ui'
  20. import { Admonition } from 'ui-patterns'
  21. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  22. import { InlineLink } from '@/components/ui/InlineLink'
  23. import { useAWSAccountCreateMutation } from '@/data/aws-accounts/aws-account-create-mutation'
  24. import type { AWSAccount } from '@/data/aws-accounts/aws-accounts-query'
  25. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  26. import { DOCS_URL } from '@/lib/constants'
  27. interface AWSPrivateLinkFormProps {
  28. account?: AWSAccount
  29. open: boolean
  30. onOpenChange: (open: boolean) => void
  31. }
  32. interface FormValues {
  33. awsAccountId: string
  34. accountName: string
  35. }
  36. export const AWSPrivateLinkForm = ({ account, open, onOpenChange }: AWSPrivateLinkFormProps) => {
  37. const isNew = !account
  38. const { data: project } = useSelectedProjectQuery()
  39. const { mutate: createAccount, isPending } = useAWSAccountCreateMutation()
  40. const form = useForm<FormValues>({
  41. defaultValues: {
  42. awsAccountId: account?.aws_account_id ?? '',
  43. accountName: account?.account_name ?? '',
  44. },
  45. })
  46. const title =
  47. account?.status === 'ASSOCIATION_ACCEPTED'
  48. ? 'This connection is active'
  49. : account?.status === 'READY'
  50. ? 'Connection is ready'
  51. : account?.status === 'CREATING'
  52. ? 'This account connection is being created'
  53. : account?.status === 'DELETING'
  54. ? 'This account is being deleted'
  55. : account?.status === 'ASSOCIATION_REQUEST_EXPIRED'
  56. ? 'Account acceptance request has expired'
  57. : account?.status === 'CREATION_FAILED'
  58. ? 'Failed to create account'
  59. : 'This account needs to be accepted by the AWS account owner.'
  60. const description =
  61. account?.status === 'ASSOCIATION_ACCEPTED'
  62. ? 'The resource share has been accepted by the AWS account owner and the connection is established.'
  63. : account?.status === 'READY'
  64. ? 'It may be waiting acceptance from the AWS account owner. Association requests are automatically deleted if not accepted within 12 hours.'
  65. : account?.status === 'ASSOCIATION_REQUEST_EXPIRED'
  66. ? 'Reconnect this account to initiate a new connection request'
  67. : account?.status === 'CREATION_FAILED'
  68. ? 'Reconnect this account to initiate a new connection request'
  69. : ''
  70. const onSubmit = (values: FormValues) => {
  71. if (!project) return
  72. if (isNew) {
  73. createAccount(
  74. {
  75. projectRef: project.ref,
  76. awsAccountId: values.awsAccountId,
  77. accountName: values.accountName,
  78. },
  79. {
  80. onSuccess: () => {
  81. toast.success('Successfully added AWS account')
  82. onOpenChange(false)
  83. },
  84. }
  85. )
  86. }
  87. }
  88. // Reset form when account changes
  89. useEffect(() => {
  90. form.reset({
  91. awsAccountId: account?.aws_account_id ?? '',
  92. accountName: account?.account_name ?? '',
  93. })
  94. }, [account, form])
  95. return (
  96. <Sheet open={open} onOpenChange={onOpenChange}>
  97. <SheetContent className="flex flex-col gap-0">
  98. <SheetHeader>
  99. <SheetTitle>{isNew ? 'Add AWS Account' : 'AWS Account Details'}</SheetTitle>
  100. <SheetDescription>
  101. Connect to your Briven project from your AWS VPC using AWS PrivateLink.{' '}
  102. <InlineLink href={`${DOCS_URL}/guides/platform/privatelink`}>Learn more</InlineLink>
  103. </SheetDescription>
  104. </SheetHeader>
  105. <Form {...form}>
  106. <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col flex-1">
  107. <SheetSection className="space-y-4 flex-1">
  108. {!isNew && account && (
  109. <>
  110. <Admonition
  111. showIcon={false}
  112. type="default"
  113. childProps={{ title: { className: 'flex-row gap-x-2 items-center' } }}
  114. // @ts-ignore
  115. title={
  116. <>
  117. <span>{title}</span>
  118. <Badge
  119. variant={
  120. account.status === 'ASSOCIATION_ACCEPTED'
  121. ? 'success'
  122. : account.status === 'READY'
  123. ? 'success'
  124. : account.status === 'CREATING'
  125. ? 'warning'
  126. : account.status === 'CREATION_FAILED' ||
  127. account.status === 'ASSOCIATION_REQUEST_EXPIRED'
  128. ? 'destructive'
  129. : 'warning'
  130. }
  131. >
  132. {account.status === 'ASSOCIATION_ACCEPTED'
  133. ? 'Connected'
  134. : account.status === 'READY'
  135. ? 'Ready'
  136. : account.status === 'CREATING'
  137. ? 'Creating'
  138. : account.status === 'CREATION_FAILED'
  139. ? 'Failed'
  140. : account.status === 'ASSOCIATION_REQUEST_EXPIRED'
  141. ? 'Expired'
  142. : account.status === 'DELETING'
  143. ? 'Deleting'
  144. : 'Unknown'}
  145. </Badge>
  146. </>
  147. }
  148. description={description}
  149. actions={
  150. account.status === 'READY' && (
  151. <Button type="default" className="w-min mt-2">
  152. <Link
  153. target="_blank"
  154. rel="noopener noreferrer"
  155. href={`${DOCS_URL}/guides/platform/privatelink#step-2-accept-resource-share`}
  156. >
  157. How to accept?
  158. </Link>
  159. </Button>
  160. )
  161. }
  162. />
  163. </>
  164. )}
  165. <FormField
  166. control={form.control}
  167. name="awsAccountId"
  168. render={({ field }) => (
  169. <FormItemLayout
  170. label="AWS Account ID"
  171. description="The ID of the AWS account you want to connect to."
  172. >
  173. <FormControl>
  174. <Input
  175. {...field}
  176. readOnly={!isNew}
  177. autoFocus={isNew}
  178. onFocus={(e) => {
  179. if (!isNew) {
  180. e.target.blur()
  181. }
  182. }}
  183. />
  184. </FormControl>
  185. </FormItemLayout>
  186. )}
  187. />
  188. <FormField
  189. control={form.control}
  190. name="accountName"
  191. render={({ field }) => (
  192. <FormItemLayout
  193. label="Account Name"
  194. description="A name for this account connection."
  195. >
  196. <FormControl>
  197. <Input
  198. {...field}
  199. readOnly={!isNew}
  200. onFocus={(e) => {
  201. if (!isNew) {
  202. e.target.blur()
  203. }
  204. }}
  205. />
  206. </FormControl>
  207. </FormItemLayout>
  208. )}
  209. />
  210. </SheetSection>
  211. <SheetFooter>
  212. <Button type="default" disabled={isPending} onClick={() => onOpenChange(false)}>
  213. Cancel
  214. </Button>
  215. {isNew && (
  216. <Button htmlType="submit" loading={isPending}>
  217. Add Account
  218. </Button>
  219. )}
  220. </SheetFooter>
  221. </form>
  222. </Form>
  223. </SheetContent>
  224. </Sheet>
  225. )
  226. }