| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { useQuery } from '@tanstack/react-query'
- import { useParams } from 'common'
- import { Loader2 } from 'lucide-react'
- import { useState } from 'react'
- import { toast } from 'sonner'
- import { cn } from 'ui'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import { EmptyStatePresentational } from 'ui-patterns/EmptyStatePresentational'
- import {
- PageSection,
- PageSectionAside,
- PageSectionContent,
- PageSectionDescription,
- PageSectionMeta,
- PageSectionSummary,
- PageSectionTitle,
- } from 'ui-patterns/PageSection'
- import { AddIntegrationDropdown } from './AddIntegrationDropdown'
- import { CreateAuth0IntegrationDialog } from './CreateAuth0Dialog'
- import { CreateAwsCognitoAuthIntegrationDialog } from './CreateAwsCognitoAuthDialog'
- import { CreateClerkAuthIntegrationDialog } from './CreateClerkAuthDialog'
- import { CreateFirebaseAuthIntegrationDialog } from './CreateFirebaseAuthDialog'
- import { CreateWorkOSIntegrationDialog } from './CreateWorkOSDialog'
- import { IntegrationCard } from './IntegrationCard'
- import {
- getIntegrationType,
- getIntegrationTypeLabel,
- INTEGRATION_TYPES,
- } from './ThirdPartyAuthForm.utils'
- import AlertError from '@/components/ui/AlertError'
- import { DocsButton } from '@/components/ui/DocsButton'
- import { InlineLink } from '@/components/ui/InlineLink'
- import { useDeleteThirdPartyAuthIntegrationMutation } from '@/data/third-party-auth/integration-delete-mutation'
- import {
- ThirdPartyAuthIntegration,
- thirdPartyAuthIntegrationsQueryOptions,
- } from '@/data/third-party-auth/integrations-query'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { DOCS_URL } from '@/lib/constants'
- import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
- import { useShortcut } from '@/state/shortcuts/useShortcut'
- export const ThirdPartyAuthForm = () => {
- const { ref: projectRef } = useParams()
- const {
- data: integrationsData,
- isPending: isLoading,
- isError,
- isSuccess,
- error,
- } = useQuery(thirdPartyAuthIntegrationsQueryOptions({ projectRef }))
- const integrations = integrationsData || []
- const [selectedIntegration, setSelectedIntegration] = useState<INTEGRATION_TYPES>()
- const [selectedIntegrationForDeletion, setSelectedIntegrationForDeletion] =
- useState<ThirdPartyAuthIntegration>()
- const [addIntegrationOpen, setAddIntegrationOpen] = useState(false)
- useShortcut(SHORTCUT_IDS.LIST_PAGE_NEW_ITEM, () => setAddIntegrationOpen(true), {
- label: 'Add provider',
- })
- const { mutateAsync: deleteIntegration } = useDeleteThirdPartyAuthIntegrationMutation()
- const { can: canUpdateConfig } = useAsyncCheckPermissions(
- PermissionAction.UPDATE,
- 'custom_config_gotrue'
- )
- if (isError) {
- return (
- <AlertError
- error={error}
- subject="Failed to retrieve auth configuration for Third-Party Auth Integrations"
- />
- )
- }
- return (
- <PageSection>
- <PageSectionMeta>
- <PageSectionSummary>
- <PageSectionTitle>Third-Party Auth</PageSectionTitle>
- <PageSectionDescription>
- Billing is based on the number of monthly active users (MAUs) requesting your API
- throughout the billing period.{' '}
- <InlineLink
- href={`${DOCS_URL}/guides/platform/manage-your-usage/monthly-active-users-third-party`}
- >
- Learn more
- </InlineLink>
- </PageSectionDescription>
- </PageSectionSummary>
- <PageSectionAside>
- <DocsButton href={`${DOCS_URL}/guides/auth/third-party/overview`} />
- <AddIntegrationDropdown
- open={addIntegrationOpen}
- onOpenChange={setAddIntegrationOpen}
- onSelectIntegrationType={setSelectedIntegration}
- />
- </PageSectionAside>
- </PageSectionMeta>
- <PageSectionContent>
- {isLoading && (
- <div
- className={cn(
- 'border rounded-sm border-default px-20 py-16 flex flex-col items-center justify-center space-y-4'
- )}
- >
- <Loader2 size={24} className="animate-spin" />
- </div>
- )}
- {isSuccess ? (
- integrations.length === 0 ? (
- <EmptyStatePresentational
- title="Add an authentication provider"
- description="Use third-party authentication systems based on JWTs to access your project."
- >
- <AddIntegrationDropdown
- align="center"
- type="default"
- onSelectIntegrationType={setSelectedIntegration}
- />
- </EmptyStatePresentational>
- ) : (
- <div className="-space-y-px">
- {integrations.map((integration) => {
- return (
- <IntegrationCard
- key={integration.id}
- integration={integration}
- canUpdateConfig={canUpdateConfig}
- onDelete={() => {
- setSelectedIntegrationForDeletion(integration)
- }}
- />
- )
- })}
- </div>
- )
- ) : null}
- <CreateFirebaseAuthIntegrationDialog
- visible={selectedIntegration === 'firebase'}
- onDelete={() => {}}
- onClose={() => setSelectedIntegration(undefined)}
- />
- <CreateAwsCognitoAuthIntegrationDialog
- visible={selectedIntegration === 'awsCognito'}
- onDelete={() => {}}
- onClose={() => setSelectedIntegration(undefined)}
- />
- <CreateAuth0IntegrationDialog
- visible={selectedIntegration === 'auth0'}
- onDelete={() => {}}
- onClose={() => setSelectedIntegration(undefined)}
- />
- <CreateClerkAuthIntegrationDialog
- visible={selectedIntegration === 'clerk'}
- onDelete={() => {}}
- onClose={() => setSelectedIntegration(undefined)}
- />
- <CreateWorkOSIntegrationDialog
- visible={selectedIntegration === 'workos'}
- onDelete={() => {}}
- onClose={() => setSelectedIntegration(undefined)}
- />
- <ConfirmationModal
- size="medium"
- visible={!!selectedIntegrationForDeletion}
- variant="destructive"
- title="Confirm to delete integration"
- confirmLabel="Delete"
- confirmLabelLoading="Deleting"
- onCancel={() => setSelectedIntegrationForDeletion(undefined)}
- onConfirm={async () => {
- if (!selectedIntegrationForDeletion) {
- return
- }
- const type = getIntegrationType(selectedIntegrationForDeletion)
- try {
- await deleteIntegration({
- projectRef: projectRef!,
- tpaId: selectedIntegrationForDeletion.id,
- })
- toast.success(`Successfully deleted ${getIntegrationTypeLabel(type)}.`)
- setSelectedIntegrationForDeletion(undefined)
- setSelectedIntegration(undefined)
- } catch (error) {
- toast.error(`Failed to delete ${getIntegrationTypeLabel(type)}.`)
- console.error(error)
- }
- }}
- >
- <p className="text-sm text-foreground-light">
- Are you sure you want to delete the{' '}
- {getIntegrationTypeLabel(getIntegrationType(selectedIntegrationForDeletion))}{' '}
- integration?
- </p>
- </ConfirmationModal>
- </PageSectionContent>
- </PageSection>
- )
- }
|