Files
df52ea7ee0 feat: Replace all toasts with sonner (#28250)
* 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>
2024-08-31 07:50:51 +08:00

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