chore(studio): security notification emails announcements (#40366)

* announcement

* fix link

* Minor clean up

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Danny WhiteandJoshen Lim authored and GitHub committed 2025-11-12 13:28:42 +11:00
1 parent e08d1f7561
commit a4928ef049
9 files changed
+86 -20

No files matched your search

@@ -3,8 +3,8 @@ import { LOCAL_STORAGE_KEYS } from 'common'
export const FEATURE_PREVIEWS = [
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS,
name: 'Security notification templates',
discussionsUrl: undefined,
name: 'Security notification emails',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/40349',
isNew: true,
isPlatformOnly: true,
},
@@ -3,11 +3,9 @@ import Image from 'next/image'
import { useParams } from 'common'
import { InlineLink } from 'components/ui/InlineLink'
import { BASE_PATH } from 'lib/constants'
import { useIsSecurityNotificationsEnabled } from './FeaturePreviewContext'
export const SecurityNotificationsPreview = () => {
const { ref } = useParams()
const isSecurityNotificationsEnabled = useIsSecurityNotificationsEnabled()
return (
<div className="text-sm text-foreground-light">
@@ -21,11 +19,7 @@ export const SecurityNotificationsPreview = () => {
<div className="space-y-2 !mt-4">
<p className=" mb-4">
Try out our expanded set of{' '}
<InlineLink
href={`/project/${ref ?? '_'}/auth/${isSecurityNotificationsEnabled ? 'email' : 'templates'}`}
>
email templates
</InlineLink>{' '}
<InlineLink href={`/project/${ref ?? '_'}/auth/templates`}>email templates</InlineLink>{' '}
with support for security-related notifications.
</p>
<p className="text-foreground">Enabling this preview will:</p>
@@ -37,8 +31,7 @@ export const SecurityNotificationsPreview = () => {
<p>
These changes are necessary to support incoming security-related notification templates.
Given that the list of our email templates is doubling in size, this change requires some
wider interface changes. Ones that we think make for a clearer experience overall. Win
win!
wider interface changes. Ones that we think make for a clearer experience overall.
</p>
</div>
</div>
@@ -6,7 +6,7 @@ import { DOCS_URL } from 'lib/constants'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
export function EmailRateLimitsAlert() {
export const EmailRateLimitsAlert = () => {
const { ref } = useParams()
return (
@@ -1,3 +0,0 @@
import { EmailRateLimitsAlert } from './EmailRateLimitsAlert'
export default EmailRateLimitsAlert
@@ -1,21 +1,26 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { ChevronRight } from 'lucide-react'
import { ChevronRight, ExternalLink, X } from 'lucide-react'
import Link from 'next/link'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'
import { useParams } from 'common'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { FEATURE_PREVIEWS } from 'components/interfaces/App/FeaturePreview/FeaturePreview.constants'
import { useIsSecurityNotificationsEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import { InlineLink } from 'components/ui/InlineLink'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { DOCS_URL } from 'lib/constants'
import {
Badge,
Button,
Card,
CardContent,
@@ -28,9 +33,13 @@ import {
TabsContent_Shadcn_,
TabsList_Shadcn_,
TabsTrigger_Shadcn_,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { TEMPLATES_SCHEMAS } from '../AuthTemplatesValidation'
import EmailRateLimitsAlert from '../EmailRateLimitsAlert'
import { EmailRateLimitsAlert } from '../EmailRateLimitsAlert'
import { slugifyTitle } from './EmailTemplates.utils'
import { TemplateEditor } from './TemplateEditor'
@@ -50,6 +59,10 @@ const NotificationsFormSchema = z.object({
),
})
const SECURITY_NOTIFICATIONS_DISCUSSIONS_URL = FEATURE_PREVIEWS.find(
(f) => f.key === LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS
)?.discussionsUrl
export const EmailTemplates = () => {
const { ref: projectRef } = useParams()
const isSecurityNotificationsEnabled = useIsSecurityNotificationsEnabled()
@@ -58,6 +71,11 @@ export const EmailTemplates = () => {
'custom_config_gotrue'
)
const [acknowledged, setAcknowledged] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.SECURITY_NOTIFICATIONS_ACKNOWLEDGED(projectRef ?? ''),
false
)
const {
data: authConfig,
error: authConfigError,
@@ -161,6 +179,60 @@ export const EmailTemplates = () => {
<div>
<ScaffoldSectionTitle className="mb-4">Security</ScaffoldSectionTitle>
{!acknowledged && (
<Admonition showIcon={false} type="tip" className="relative mb-6">
<Tooltip>
<TooltipTrigger
onClick={() => setAcknowledged(true)}
className="absolute top-3 right-3 opacity-30 hover:opacity-100 transition-opacity"
>
<X size={14} className="text-foreground-light" />
</TooltipTrigger>
<TooltipContent side="bottom">Dismiss</TooltipContent>
</Tooltip>
<div className="flex flex-col md:flex-row md:items-center gap-y-2 md:gap-x-8 justify-between px-2 py-1">
<div className="flex flex-col gap-y-0.5">
<div className="flex flex-col gap-y-2 items-start">
<Badge variant="success" className="-ml-0.5">
NEW
</Badge>
<p className="text-sm">
Notify users about security-sensitive actions on their accounts
</p>
</div>
<p className="text-sm text-foreground-lighter text-balance">
We’ve expanded our email templates to handle security-sensitive actions.
The list of templates will continue to grow as our feature-set changes
{SECURITY_NOTIFICATIONS_DISCUSSIONS_URL && (
<>
{' '}
and as we{' '}
<InlineLink
href={SECURITY_NOTIFICATIONS_DISCUSSIONS_URL}
target="_blank"
>
gather feedback
</InlineLink>{' '}
from our community
</>
)}
.
</p>
</div>
<Button
asChild
type="default"
icon={<ExternalLink strokeWidth={1.5} />}
className="mt-2"
>
<Link href={`${DOCS_URL}/guides/auth/auth-email-templates`} target="_blank">
Docs
</Link>
</Button>
</div>
</Admonition>
)}
<Form_Shadcn_ {...notificationsForm}>
<form onSubmit={notificationsForm.handleSubmit(onSubmit)} className="space-y-4">
<Card>
@@ -206,7 +206,7 @@ export const ChartConfig = ({
<Tooltip>
<TooltipTrigger
onClick={() => setAcknowledged(true)}
className="absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity"
className="absolute top-3 right-3 opacity-30 group-hover:opacity-100 transition-opacity"
>
<X size={14} className="text-foreground-light" />
</TooltipTrigger>
@@ -234,7 +234,7 @@ const RedirectToTemplates = () => {
<ScaffoldSection isFullWidth>
{/* Only show title if there is an another section above */}
{showConfigurationSection && (
<ScaffoldSectionTitle className="mb-4">Contents</ScaffoldSectionTitle>
<ScaffoldSectionTitle className="mb-4">Content</ScaffoldSectionTitle>
)}
<Card>
<TemplateEditor template={template} />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 124 KiB

@@ -40,6 +40,10 @@ export const LOCAL_STORAGE_KEYS = {
SQL_EDITOR_SECTION_STATE: (ref: string) => `sql-editor-section-state-${ref}`,
SQL_EDITOR_SORT: (ref: string) => `sql-editor-sort-${ref}`,
// Key to track if the user has acknowledged the security notifications preview
SECURITY_NOTIFICATIONS_ACKNOWLEDGED: (ref: string) =>
`security-notifications-acknowledged-${ref}`,
LOG_EXPLORER_SPLIT_SIZE: 'supabase_log-explorer-split-size',
GRAPHIQL_RLS_BYPASS_WARNING: 'graphiql-rls-bypass-warning-dismissed',
CLS_DIFF_WARNING: 'cls-diff-warning-dismissed',