mirror of
https://github.com/supabase/supabase.git
synced 2026-10-19 08:09:11 +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>
125 lines
4.6 KiB
TypeScript
125 lines
4.6 KiB
TypeScript
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { useQueryClient } from '@tanstack/react-query'
|
|
import { useEffect } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import { useParams } from 'common'
|
|
import {
|
|
ScaffoldSection,
|
|
ScaffoldSectionContent,
|
|
ScaffoldSectionDetail,
|
|
} from 'components/layouts/Scaffold'
|
|
import { FormActions } from 'components/ui/Forms/FormActions'
|
|
import { FormPanel } from 'components/ui/Forms/FormPanel'
|
|
import { FormSection, FormSectionContent } from 'components/ui/Forms/FormSection'
|
|
import { useOrganizationUpdateMutation } from 'data/organizations/organization-update-mutation'
|
|
import { invalidateOrganizationsQuery } from 'data/organizations/organizations-query'
|
|
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
|
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
|
|
import { Form, Input } from 'ui'
|
|
|
|
const BillingEmail = () => {
|
|
const { slug } = useParams()
|
|
const queryClient = useQueryClient()
|
|
const selectedOrganization = useSelectedOrganization()
|
|
const { name, billing_email } = selectedOrganization ?? {}
|
|
|
|
const formId = 'org-billing-email'
|
|
const initialValues = { billing_email: billing_email ?? '' }
|
|
|
|
const canUpdateOrganization = useCheckPermissions(PermissionAction.UPDATE, 'organizations')
|
|
const canReadBillingEmail = useCheckPermissions(
|
|
PermissionAction.BILLING_READ,
|
|
'stripe.subscriptions'
|
|
)
|
|
const { mutate: updateOrganization, isLoading: isUpdating } = useOrganizationUpdateMutation()
|
|
|
|
const onUpdateOrganizationEmail = async (values: any, { resetForm }: any) => {
|
|
if (!canUpdateOrganization) {
|
|
return toast.error('You do not have the required permissions to update this organization')
|
|
}
|
|
if (!slug) return console.error('Slug is required')
|
|
if (!name) return console.error('Organization name is required')
|
|
|
|
updateOrganization(
|
|
{
|
|
slug,
|
|
name,
|
|
billing_email: values.billing_email,
|
|
},
|
|
{
|
|
onSuccess: ({ billing_email }) => {
|
|
resetForm({ values: { billing_email }, initialValues: { billing_email } })
|
|
invalidateOrganizationsQuery(queryClient)
|
|
toast.success('Successfully saved settings')
|
|
},
|
|
}
|
|
)
|
|
}
|
|
|
|
if (!canReadBillingEmail) return null
|
|
|
|
return (
|
|
<ScaffoldSection>
|
|
<ScaffoldSectionDetail>
|
|
<div className="sticky space-y-2 top-12">
|
|
<p className="text-foreground text-base m-0">Email Recipient</p>
|
|
<p className="text-sm text-foreground-light m-0">
|
|
All billing correspondence will go to this email
|
|
</p>
|
|
</div>
|
|
</ScaffoldSectionDetail>
|
|
<ScaffoldSectionContent>
|
|
<Form id={formId} initialValues={initialValues} onSubmit={onUpdateOrganizationEmail}>
|
|
{({ handleReset, values, initialValues, resetForm }: any) => {
|
|
const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
|
|
|
|
// [Alaister] although this "technically" is breaking the rules of React hooks
|
|
// it won't error because the hooks are always rendered in the same order
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useEffect(() => {
|
|
const values = { billing_email: billing_email ?? '' }
|
|
resetForm({ values, initialValues: values })
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [slug])
|
|
|
|
return (
|
|
<FormPanel
|
|
footer={
|
|
<div className="flex py-4 px-8">
|
|
<FormActions
|
|
form={formId}
|
|
isSubmitting={isUpdating}
|
|
hasChanges={hasChanges}
|
|
handleReset={handleReset}
|
|
helper={
|
|
!canUpdateOrganization
|
|
? "You need additional permissions to manage this organization's settings"
|
|
: undefined
|
|
}
|
|
/>
|
|
</div>
|
|
}
|
|
>
|
|
<FormSection>
|
|
<FormSectionContent fullWidth loading={false}>
|
|
<Input
|
|
id="billing_email"
|
|
size="small"
|
|
label="Email address"
|
|
type="text"
|
|
disabled={!canUpdateOrganization}
|
|
/>
|
|
</FormSectionContent>
|
|
</FormSection>
|
|
</FormPanel>
|
|
)
|
|
}}
|
|
</Form>
|
|
</ScaffoldSectionContent>
|
|
</ScaffoldSection>
|
|
)
|
|
}
|
|
|
|
export default BillingEmail
|