simplify auth email template paygate

This commit is contained in:
Danny White committed 2026-05-04 17:04:19 +10:00
1 parent 808a52000f
commit 09fc2bafd7
3 files changed
+92 -36

No files matched your search

@@ -1,8 +1,15 @@
import { ChevronDown } from 'lucide-react'
import Link from 'next/link'
import { Button } from 'ui'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
interface CustomEmailTemplateRestrictionAdmonitionProps {
projectRef?: string
@@ -11,27 +18,41 @@ interface CustomEmailTemplateRestrictionAdmonitionProps {
export const CustomEmailTemplateRestrictionAdmonition = ({
projectRef,
}: CustomEmailTemplateRestrictionAdmonitionProps) => {
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const organizationSlug = selectedOrganization?.slug ?? '_'
return (
<Admonition
type="default"
layout="responsive"
title="Custom templates require Custom SMTP or Pro"
description="This project can view the default email templates, but editing subject lines or HTML is disabled while it uses Supabase's built-in email service on the Free plan. Set up Custom SMTP, configure a send-email hook, or upgrade to Pro to customise templates."
title="Set up Custom SMTP to edit templates"
description="Default templates are read-only while this project uses Supabase's built-in email service."
actions={
<>
<Button asChild type="default">
<Button asChild type="primary">
<Link href={`/project/${projectRef ?? '_'}/auth/smtp`}>Set up SMTP</Link>
</Button>
<Button asChild type="default">
<Link href={`/project/${projectRef ?? '_'}/auth/hooks`}>Configure hook</Link>
</Button>
<UpgradePlanButton
source="authEmailTemplates"
variant="default"
featureProposition="customise Auth email templates"
>
Upgrade to Pro
</UpgradePlanButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="default" iconRight={<ChevronDown size={14} />}>
Other options
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52">
<DropdownMenuItem asChild>
<Link
href={`/org/${organizationSlug}/billing?panel=subscriptionPlan&source=authEmailTemplates`}
>
Upgrade to Pro
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/project/${projectRef ?? '_'}/auth/hooks`}>
Configure send-email hook
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
}
/>
@@ -24,6 +24,7 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { SpamValidation } from './SpamValidation'
import { PreventNavigationOnUnsavedChanges } from '@/components/ui-patterns/Dialogs/PreventNavigationOnUnsavedChanges'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import CodeEditor from '@/components/ui/CodeEditor/CodeEditor'
import { TwoOptionToggle } from '@/components/ui/TwoOptionToggle'
import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
@@ -169,6 +170,11 @@ export const TemplateEditor = ({ template, isReadOnly = false }: TemplateEditorP
const baselineBodyValue = (authConfig && authConfig[messageSlug]) ?? ''
const hasFormChanges = JSON.stringify(formValues) !== JSON.stringify(baselineValues)
const hasChanges = hasFormChanges || baselineBodyValue !== bodyValue
const saveChangesTooltip = !canEdit
? 'Set up Custom SMTP to edit and save templates'
: !hasChanges
? 'Make a change before saving'
: undefined
// Function to insert text at cursor position
const insertTextAtCursor = (text: string) => {
@@ -229,6 +235,10 @@ export const TemplateEditor = ({ template, isReadOnly = false }: TemplateEditorP
if (!hasChanges) setValidationResult(undefined)
}, [hasChanges])
useEffect(() => {
if (!canEdit) setActiveView('preview')
}, [canEdit])
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
@@ -282,8 +292,13 @@ export const TemplateEditor = ({ template, isReadOnly = false }: TemplateEditorP
width={60}
options={['preview', 'source']}
activeOption={activeView}
onClickOption={(option) => setActiveView(option as 'source' | 'preview')}
onClickOption={(option) => {
if (!canEdit && option === 'source') return
setActiveView(option as 'source' | 'preview')
}}
borderOverride="border-muted"
disabledOptions={!canEdit ? ['source'] : []}
disabledOptionTooltip="Set up Custom SMTP to edit the source"
/>
</div>
{activeView === 'source' ? (
@@ -357,14 +372,15 @@ export const TemplateEditor = ({ template, isReadOnly = false }: TemplateEditorP
Cancel
</Button>
)}
<Button
<ButtonTooltip
type="primary"
htmlType="submit"
disabled={!canEdit || isSavingTemplate || !hasChanges}
loading={isSavingTemplate}
tooltip={{ content: { side: 'bottom', text: saveChangesTooltip } }}
>
Save changes
</Button>
</ButtonTooltip>
</CardFooter>
</>
)}
+37 -18
View File
@@ -1,4 +1,4 @@
import { cn } from 'ui'
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
interface TwoOptionToggleProps {
options: string[]
@@ -6,6 +6,8 @@ interface TwoOptionToggleProps {
activeOption: string
onClickOption: (value: string) => void
borderOverride: string
disabledOptions?: string[]
disabledOptionTooltip?: string
}
export const TwoOptionToggle = ({
@@ -14,6 +16,8 @@ export const TwoOptionToggle = ({
activeOption,
onClickOption,
borderOverride = 'border-stronger',
disabledOptions = [],
disabledOptionTooltip,
}: TwoOptionToggleProps) => {
const buttonStyle = (
isActive: boolean
@@ -36,28 +40,43 @@ export const TwoOptionToggle = ({
'transition-all ease-in-out border border-strong'
)}
/>
{options.map((option, index: number) => (
<span
key={`toggle_${index}`}
style={{ width: width + 1 }}
className={`
${activeOption === option ? 'text-foreground' : 'text-foreground-light'}
${index === 0 ? 'right-0' : 'left-0'}
${buttonStyle(activeOption === option)}
cursor-pointer
`}
onClick={() => onClickOption(option)}
>
{options.map((option, index: number) => {
const isDisabled = disabledOptions.includes(option)
const optionButton = (
<span
key={`toggle_${index}`}
style={{ width: width + 1 }}
className={cn(
'capitalize hover:text-foreground',
activeOption === option ? 'text-foreground' : 'text-foreground-light'
activeOption === option ? 'text-foreground' : 'text-foreground-light',
index === 0 ? 'right-0' : 'left-0',
buttonStyle(activeOption === option),
isDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'
)}
onClick={() => {
if (!isDisabled) onClickOption(option)
}}
>
{option}
<span
className={cn(
'capitalize hover:text-foreground',
activeOption === option ? 'text-foreground' : 'text-foreground-light',
isDisabled && 'hover:text-foreground-light'
)}
>
{option}
</span>
</span>
</span>
))}
)
if (!isDisabled || !disabledOptionTooltip) return optionButton
return (
<Tooltip key={`toggle_${index}`}>
<TooltipTrigger asChild>{optionButton}</TooltipTrigger>
<TooltipContent side="top">{disabledOptionTooltip}</TooltipContent>
</Tooltip>
)
})}
</div>
)
}