mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 15:49:09 +03:00
* Update the design of the sonner toasts. Add the close button by default. * Migrate studio and www apps to use the SonnerToaster. * Migrate all toasts from studio. * Migrate all leftover toasts in studio. * Add a new toast component with progress. Use it in studio. * Migrate the design-system app. * Refactor the consent toast to use sonner. * Switch docs to use the new sonner toasts. * Remove toast examples from the design-system app. * Remove all toast-related components and old code. * Fix the progress bar in the toast progress component. Also make the bottom components vertically centered. * Fix the width of the toast progress. * Use text-foreground-lighter instead of muted for ToastProgress text * Rename ToastProgress to SonnerProgress. * Shorten the text in sonner progress. * Use the correct classes for the close button. Add a const var for the default toast duration. Remove the custom width class from sonner. * Set the position for all progress toasts to bottom right. Set the duration for all toasts to the default (when reusing a toast id from loading/progress toast, the duration is set to infinity). * Fix the playwright tests. * Refactor imports to use ui instead of @ui. * Change all imports of react-hot-toast with sonner. These components were merged since the last commit to this branch. * Remove react-hot-toast lib. --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com> Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com>
174 lines
6.0 KiB
TypeScript
174 lines
6.0 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { Loader2 } from 'lucide-react'
|
|
import { useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import { useParams } from 'common'
|
|
import AlertError from 'components/ui/AlertError'
|
|
import { FormHeader } from 'components/ui/Forms/FormHeader'
|
|
import { useDeleteThirdPartyAuthIntegrationMutation } from 'data/third-party-auth/integration-delete-mutation'
|
|
import {
|
|
ThirdPartyAuthIntegration,
|
|
useThirdPartyAuthIntegrationsQuery,
|
|
} from 'data/third-party-auth/integrations-query'
|
|
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
|
import { useFlag } from 'hooks/ui/useFlag'
|
|
import { cn } from 'ui'
|
|
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
|
import { AddIntegrationDropdown } from './AddIntegrationDropdown'
|
|
import { CreateAuth0IntegrationDialog } from './CreateAuth0Dialog'
|
|
import { CreateAwsCognitoAuthIntegrationDialog } from './CreateAwsCognitoAuthDialog'
|
|
import { CreateFirebaseAuthIntegrationDialog } from './CreateFirebaseAuthDialog'
|
|
import { IntegrationCard } from './IntegrationCard'
|
|
import {
|
|
getIntegrationType,
|
|
getIntegrationTypeLabel,
|
|
INTEGRATION_TYPES,
|
|
} from './ThirdPartyAuthForm.utils'
|
|
|
|
export const ThirdPartyAuthForm = () => {
|
|
const thirdPartyAuthEnabled = useFlag('thirdPartyAuth')
|
|
const { ref: projectRef } = useParams()
|
|
const {
|
|
data: integrationsData,
|
|
isLoading,
|
|
isError,
|
|
isSuccess,
|
|
error,
|
|
} = useThirdPartyAuthIntegrationsQuery({ projectRef })
|
|
const integrations = integrationsData || []
|
|
|
|
const [selectedIntegration, setSelectedIntegration] = useState<INTEGRATION_TYPES>()
|
|
const [selectedIntegrationForDeletion, setSelectedIntegrationForDeletion] =
|
|
useState<ThirdPartyAuthIntegration>()
|
|
|
|
const { mutateAsync: deleteIntegration } = useDeleteThirdPartyAuthIntegrationMutation()
|
|
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
|
|
|
|
if (!thirdPartyAuthEnabled) {
|
|
return null
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<AlertError
|
|
error={error}
|
|
subject="Failed to retrieve auth configuration for Third Party Auth Integrations"
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="pb-4">
|
|
<FormHeader
|
|
title="Third Party Auth"
|
|
className="mb-1"
|
|
description="Use third-party authentication (TPA) systems based on JWTs to access your project."
|
|
actions={
|
|
integrations.length !== 0 ? (
|
|
<AddIntegrationDropdown onSelectIntegrationType={setSelectedIntegration} />
|
|
) : null
|
|
}
|
|
docsUrl="https://supabase.com/docs/guides/auth/third-party/overview"
|
|
/>
|
|
<div className="prose text-sm mb-6 max-w-full">
|
|
<span>
|
|
Billing is based on the number of monthly active users (MAUs) requesting your API
|
|
throughout the billing period (50 included then you'll be charged{' '}
|
|
</span>
|
|
<span className="text-brand">$0.00325</span>
|
|
<span> per MAU).</span>
|
|
</div>
|
|
{isLoading && (
|
|
<div
|
|
className={cn(
|
|
'border rounded 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 ? (
|
|
<div
|
|
className={cn(
|
|
'border rounded border-default px-20 py-16 flex flex-col items-center justify-center space-y-4'
|
|
)}
|
|
>
|
|
<p className="text-sm text-foreground-light">No providers configured yet</p>
|
|
<AddIntegrationDropdown
|
|
buttonText="Add a new integration"
|
|
onSelectIntegrationType={setSelectedIntegration}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<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)}
|
|
/>
|
|
|
|
<ConfirmationModal
|
|
visible={!!selectedIntegrationForDeletion}
|
|
variant="destructive"
|
|
title="Confirm to delete"
|
|
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="py-4 text-sm text-foreground-light">
|
|
{`Are you sure you want to delete the ${getIntegrationTypeLabel(getIntegrationType(selectedIntegrationForDeletion))} integration?`}
|
|
</p>
|
|
</ConfirmationModal>
|
|
</div>
|
|
)
|
|
}
|