Files
Joshen Lim fcfb0f0222 Refactor all usage of form.watch to either useWatch or subscribe (#48436)
## Context

Replaces all usage of `form.watch()` to use `useWatch` instead + follows
the "name what you watch" convention as specified in the react-hook-form
skills.

There's also a small refactor in `SmtpForm.tsx` which removes the
unnecessary use of a `useState` to track if SMTP is enabled or not

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Updated many Studio forms to watch specific fields more precisely,
improving live UI updates for previews, warnings, conditional sections,
and validation messages.
* Enhanced responsiveness across settings, authentication, billing,
storage, integrations, and support flows while keeping save/update
behavior the same.
* **Refined Experiences**
* Improved the analytics table creation flow with tighter, enum-based
column type validation and structured, type-specific column options.
* **Preserved Behavior**
* Maintained existing permission checks, submission flows, and
account-management workflows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-30 11:45:40 +08:00

211 lines
7.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { useEffect } from 'react'
import { useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
import { Button, Card, CardContent, CardFooter, Form, FormControl, FormField, Switch } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
PageSection,
PageSectionContent,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { AlertError } from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
import { useAuthConfigQuery } from '@/data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation'
import { useTablesQuery } from '@/data/tables/tables-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
const schema = z.object({
AUDIT_LOG_DISABLE_POSTGRES: z.boolean(),
})
const AUDIT_LOG_ENTRIES_TABLE = 'audit_log_entries'
export const AuditLogsForm = () => {
const { ref: projectRef } = useParams()
const { data: project } = useSelectedProjectQuery()
const { can: canUpdateConfig } = useAsyncCheckPermissions(
PermissionAction.UPDATE,
'custom_config_gotrue'
)
const { data: tables = [] } = useTablesQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
includeColumns: false,
schema: 'auth',
})
const auditLogTable = tables.find((x) => x.name === AUDIT_LOG_ENTRIES_TABLE)
const {
data: authConfig,
error: authConfigError,
isError,
isPending: isLoading,
} = useAuthConfigQuery({ projectRef })
const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useAuthConfigUpdateMutation({
onError: (error) => {
toast.error(`Failed to update audit logs: ${error?.message}`)
},
onSuccess: () => {
toast.success('Successfully updated audit logs settings')
},
})
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { AUDIT_LOG_DISABLE_POSTGRES: false },
})
const { isDirty } = form.formState
const { AUDIT_LOG_DISABLE_POSTGRES: formValueDisablePostgres } = useWatch({
control: form.control,
})
const currentlyDisabled = authConfig?.AUDIT_LOG_DISABLE_POSTGRES ?? false
const isDisabling = !currentlyDisabled && formValueDisablePostgres
const onSubmitAuditLogs = (values: any) => {
if (!projectRef) return console.error('Project ref is required')
updateAuthConfig({ projectRef: projectRef, config: values })
}
useEffect(() => {
if (authConfig) {
form.reset({ AUDIT_LOG_DISABLE_POSTGRES: authConfig?.AUDIT_LOG_DISABLE_POSTGRES ?? false })
}
}, [authConfig])
if (isError) {
return (
<PageSection>
<PageSectionContent>
<AlertError
error={authConfigError}
subject="Failed to retrieve auth configuration for hooks"
/>
</PageSectionContent>
</PageSection>
)
}
if (isLoading) {
return (
<PageSection>
<PageSectionContent>
<GenericSkeletonLoader />
</PageSectionContent>
</PageSection>
)
}
return (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Settings</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmitAuditLogs)} className="space-y-4">
<Card>
<CardContent>
<FormField
control={form.control}
name="AUDIT_LOG_DISABLE_POSTGRES"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Write audit logs to the database"
description={
<p className="text-sm prose text-foreground-lighter max-w-full">
When enabled, audit logs are written to the{' '}
<InlineLink
target="_blank"
rel="noopener noreferrer"
href={`/project/${projectRef}/editor/${auditLogTable?.id}`}
>
<code className="text-code-inline">{AUDIT_LOG_ENTRIES_TABLE}</code>
</InlineLink>{' '}
table.
<br />
You can disable this to reduce disk usage while still accessing logs
through the{' '}
<InlineLink
href={`/project/${projectRef}/logs/explorer?q=select%0A++cast(timestamp+as+datetime)+as+timestamp%2C%0A++event_message%2C+metadata+%0Afrom+auth_audit_logs+%0Alimit+10%0A`}
>
Auth logs
</InlineLink>
.
</p>
}
>
<FormControl>
<Switch
checked={!field.value}
onCheckedChange={(value) => field.onChange(!value)}
disabled={!canUpdateConfig}
/>
</FormControl>
</FormItemLayout>
)}
/>
{isDisabling && (
<Admonition
type="warning"
className="mt-4"
title="Disabling PostgreSQL storage will not automatically migrate or transfer existing audit log data"
description={
<p>
Future audit logs will only appear in the project’s{' '}
<InlineLink
href={`/project/${projectRef}/logs/explorer?q=select%0A++cast(timestamp+as+datetime)+as+timestamp%2C%0A++event_message%2C+metadata+%0Afrom+auth_audit_logs+%0Alimit+10%0A`}
>
auth logs
</InlineLink>
. You are responsible for backing up, copying, or migrating existing data
from the{' '}
<code className="text-code-inline break-keep!">
{AUDIT_LOG_ENTRIES_TABLE}
</code>{' '}
table if needed.
</p>
}
/>
)}
</CardContent>
<CardFooter className="justify-end space-x-2">
{isDirty && (
<Button variant="default" onClick={() => form.reset()}>
Cancel
</Button>
)}
<Button
variant="primary"
type="submit"
disabled={!canUpdateConfig || isUpdatingConfig || !isDirty}
loading={isUpdatingConfig}
>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form>
</PageSectionContent>
</PageSection>
)
}