mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
simplify auth email template paygate
This commit is contained in:
1 parent
808a52000f
commit
09fc2bafd7
3 files changed
+92
-36
No files matched your search
+36
-15
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user