Files
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

162 lines
5.2 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { useParams } from 'common'
import dayjs from 'dayjs'
import { useEffect } from 'react'
import { useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
cn,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
Form,
FormControl,
FormField,
Input,
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
import { useUserUpdateMutation } from '@/data/auth/user-update-mutation'
import { User } from '@/data/auth/users-infinite-query'
interface BanUserModalProps {
visible: boolean
user: User
onClose: () => void
}
export const BanUserModal = ({ visible, user, onClose }: BanUserModalProps) => {
const { ref: projectRef } = useParams()
const { mutate: updateUser, isPending: isBanningUser } = useUserUpdateMutation({
onSuccess: (_, vars) => {
const bannedUntil = dayjs()
.add(Number(vars.banDuration), 'hours')
.format('DD MMM YYYY HH:mm (ZZ)')
toast.success(`User banned successfully until ${bannedUntil}`)
onClose()
},
})
const FormSchema = z.object({
value: z.string().min(1, { message: 'Please provide a duration' }),
unit: z.enum(['hours', 'days']),
})
type FormType = z.infer<typeof FormSchema>
const defaultValues: FormType = { value: '24', unit: 'hours' }
const form = useForm<FormType>({
mode: 'onBlur',
reValidateMode: 'onChange',
resolver: zodResolver(FormSchema),
defaultValues,
})
const [value, unit] = useWatch({ control: form.control, name: ['value', 'unit'] })
const bannedUntil = dayjs().add(Number(value), unit).format('DD MMM YYYY HH:mm (ZZ)')
const onSubmit = (data: FormType) => {
if (projectRef === undefined) return console.error('Project ref is required')
if (user.id === undefined) {
return toast.error(`Failed to ban user: User ID not found`)
}
const durationHours = data.unit === 'hours' ? Number(data.value) : Number(data.value) * 24
updateUser({
projectRef,
userId: user.id,
banDuration: durationHours,
})
}
useEffect(() => {
if (visible) form.reset(defaultValues)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible])
return (
<Dialog open={visible} onOpenChange={() => onClose()}>
<DialogContent size="small">
<DialogHeader>
<DialogTitle>Confirm to ban user</DialogTitle>
</DialogHeader>
<DialogSectionSeparator />
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<DialogSection className="flex flex-col gap-y-3">
<p className="text-sm">
This will revoke the user's access to your project and prevent them from logging in
for a specified duration.
</p>
<div className="flex items-start gap-x-2 [&>div:first-child]:grow">
<FormField
control={form.control}
name="value"
render={({ field }) => (
<FormItemLayout label="Set a ban duration">
<FormControl>
<Input {...field} />
</FormControl>
</FormItemLayout>
)}
/>
<FormField
control={form.control}
name="unit"
render={({ field }) => (
<FormItemLayout className="[&>div>div]:mt-0 mt-[29px]">
<FormControl>
<Select
{...field}
aria-label="Duration unit"
value={field.value}
onValueChange={(value) =>
form.setValue('unit', value as 'hours' | 'days')
}
>
<SelectTrigger className="capitalize w-24">{field.value}</SelectTrigger>
<SelectContent>
<SelectItem value="hours">Hours</SelectItem>
<SelectItem value="days">Days</SelectItem>
</SelectContent>
</Select>
</FormControl>
</FormItemLayout>
)}
/>
</div>
<div>
<p className="text-sm text-foreground-lighter">
This user will not be able to log in until:
</p>
<p className={cn('text-sm', !value && 'text-foreground-light')}>
{!!value ? bannedUntil : 'Invalid duration set'}
</p>
</div>
</DialogSection>
<DialogFooter>
<Button disabled={isBanningUser} onClick={() => onClose()}>
Cancel
</Button>
<Button variant="warning" type="submit" loading={isBanningUser}>
Confirm ban
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
)
}