Files
supabase/apps/studio/components/interfaces/Settings/General/DashboardPreferences.tsx
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

203 lines
7.1 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { HelpCircle } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
Card,
CardContent,
CardFooter,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
DialogTrigger,
Form,
FormControl,
FormField,
} 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, ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
import { InlineLink } from '@/components/ui/InlineLink'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
const formSchema = z.object({
defaultDatabase: z.string().optional(),
})
export type DashboardPreference = z.infer<typeof formSchema>
const DEFAULT_PREFERENCE: DashboardPreference = {
defaultDatabase: undefined,
}
/**
* [Joshen] JFYI am not convinced about the UX of this, will iterate over time
* and only release to public when we're satisfied with how it behaves
* - Where should "Dashboard preferences" live
* - Preferences currently only apply to the user via local storage until we have middleware support that will persist the setting on the project for all users
* - Should selecting which database to run queries for dashboard be an option for users to configure (or for us to just default to)
* - I'd love for this to work seamlessly, but main concern atm is latency which is region dependent
* - Also, current database logic only applies to Table Editor atm, will need to extend it further to other pages
*/
export const DashboardPreferences = () => {
const { ref: projectRef } = useParams()
const [dashboardPreferences, setDashboardPreferences, { isLoading }] =
useLocalStorageQuery<DashboardPreference>(
LOCAL_STORAGE_KEYS.DASHBOARD_PREFERENCES(projectRef ?? '_'),
DEFAULT_PREFERENCE
)
const { isPending } = useReadReplicasQuery({ projectRef })
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: dashboardPreferences,
values: dashboardPreferences,
mode: 'onSubmit',
reValidateMode: 'onBlur',
})
const onSubmit = async (values: DashboardPreference) => {
if (!projectRef) return console.error('Ref is required')
setDashboardPreferences(values)
form.reset(values)
toast.success('Successfully saved dashboard preferences!')
}
return (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle id="queries">Queries</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent className="flex flex-col gap-y-4">
{/* [Joshen] Ideally we're able to persist this for all users in the project, but will need support in our middleware */}
<Admonition
type="note"
description="These preferences control only your experience in the dashboard. Other members of this project will not be affected."
/>
{isLoading ? (
<Card>
<CardContent>
<GenericSkeletonLoader />
</CardContent>
</Card>
) : (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<Card>
<CardContent>
<FormField
control={form.control}
name="defaultDatabase"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Preferred database for dashboard queries"
description={
<p>
All read queries from the dashboard will run against the selected
database by default
<DashboardQueriesDialog />
</p>
}
className="[&>div]:md:w-1/2"
>
{isPending ? (
<ShimmeringLoader />
) : (
<FormControl>
{/* [Joshen] Need to disable unhealthy replicas */}
<DatabaseSelector
isForm
buttonProps={{ size: 'small', className: 'w-full' }}
selectedDatabaseId={field.value ?? projectRef}
onSelectId={(id) =>
field.onChange(id === projectRef ? undefined : id)
}
/>
</FormControl>
)}
</FormItemLayout>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{form.formState.isDirty && (
<Button type="button" onClick={() => form.reset(dashboardPreferences)}>
Cancel
</Button>
)}
<Button variant="primary" type="submit" disabled={!form.formState.isDirty}>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form>
)}
</PageSectionContent>
</PageSection>
)
}
const DashboardQueriesDialog = () => {
const { ref } = useParams()
return (
<Dialog>
<DialogTrigger className="ml-1 translate-y-0.5">
<HelpCircle size={14} className="hover:text-foreground transition" />
</DialogTrigger>
<DialogContent aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>How does the dashboard interact with your project's database?</DialogTitle>
</DialogHeader>
<DialogSectionSeparator />
<DialogSection className="flex flex-col gap-y-2">
<p className="text-sm">
The dashboard queries your project's database to display data across various interfaces,
such as the <InlineLink href={`/project/${ref}/editor`}>Table Editor</InlineLink>, the{' '}
<InlineLink href={`/project/${ref}/auth/users`}>Auth Users</InlineLink> page, and more.
</p>
<p className="text-sm">
You can route these queries to a read replica instead, which will help reduce load on
your primary database.
</p>
</DialogSection>
<DialogFooter>
<DialogClose asChild className="opacity-100">
<Button>Understood</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}