Files
supabase/apps/studio/components/interfaces/APIKeys/CreatePublishableAPIKeyDialog.tsx
Danny White 498d051d88 feat(studio): add project settings shortcuts (#46352)
## What kind of change does this PR introduce?

Feature. Resolves FE-3417.

## What is the current behavior?

Project Settings has a top-level `G then ,` shortcut, but its
subnavigation and repeated key/log drain actions do not have scoped
keyboard shortcuts or visible shortcut tooltips.

| Area | Current behaviour |
| --- | --- |
| Project Settings sidebar | Routes are click-only once users are inside
Settings. |
| API/JWT keys | Creation buttons do not expose keyboard shortcuts. |
| Log Drains | Add/save destination actions do not expose keyboard
shortcuts. |

## What is the new behavior?

Adds scoped Project Settings navigation chords, shortcut tooltips on the
sidebar rows, and page/action shortcuts for API keys, JWT standby keys,
and Log Drains.

| Area | New shortcut coverage |
| --- | --- |
| Project Settings sidebar | `S then G/C/I/N/W/K/J/L/A/D` for eligible
in-section routes. |
| API Keys | `Shift+P` and `Shift+S` open the publishable/secret key
dialogs; `Mod+Enter` submits the open dialog. |
| JWT Keys | `Shift+N` opens Create standby key; `Mod+Enter` submits the
open dialog. |
| Log Drains | `Shift+N` adds a destination when the primary action is
available; `Mod+Enter` saves the open destination sheet. |


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

* **New Features**
* Added keyboard shortcuts for Project Settings navigation and for
actions in API Keys, JWT Keys, and Log Drains (open, create/submit).

* **Improvements**
* Dialogs and forms now support keyboard-triggered open and submit
actions with improved enable/disable gating and updated settings menu
composition; shortcuts appear in the shortcuts reference.

* **Tests**
* Added tests covering shortcut wiring and shortcut-driven open/submit
behaviors across dialogs and action panels.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46352?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ali Waseem <waseema393@gmail.com>
2026-05-26 15:48:50 +00:00

160 lines
4.9 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { Plus } from 'lucide-react'
import { useParams } from 'next/navigation'
import { parseAsString, useQueryState } from 'nuqs'
import { useRef } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
Form,
FormControl,
FormField,
Input,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
import { Shortcut } from '@/components/ui/Shortcut'
import { useAPIKeyCreateMutation } from '@/data/api-keys/api-key-create-mutation'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
const FORM_ID = 'create-publishable-api-key'
const SCHEMA = z.object({
name: z.string(),
description: z.string().trim(),
})
export interface CreatePublishableAPIKeyDialogProps {
projectRef: string
}
export const CreatePublishableAPIKeyDialog = () => {
const params = useParams()
const projectRef = params?.ref as string
const formRef = useRef<HTMLFormElement>(null)
const [visible, setVisible] = useQueryState('new', parseAsString.withDefault(''))
const onOpenChange = (value: boolean) => {
if (value) setVisible('publishable')
else setVisible('')
}
const openDialog = () => setVisible('publishable')
const defaultValues = { name: '', description: '' }
const form = useForm<z.infer<typeof SCHEMA>>({
resolver: zodResolver(SCHEMA),
defaultValues: {
name: '',
description: '',
},
})
const { mutate: createAPIKey, isPending: isCreatingAPIKey } = useAPIKeyCreateMutation()
const onSubmit: SubmitHandler<z.infer<typeof SCHEMA>> = async (values) => {
createAPIKey(
{
projectRef,
type: 'publishable',
name: values.name,
description: values.description,
},
{
onSuccess: () => {
form.reset(defaultValues)
onOpenChange(false)
},
}
)
}
return (
<Dialog open={visible === 'publishable'} onOpenChange={onOpenChange}>
<Shortcut
id={SHORTCUT_IDS.API_KEYS_NEW_PUBLISHABLE}
onTrigger={openDialog}
side="bottom"
tooltipOpen={visible === 'publishable' ? false : undefined}
>
<Button type="default" icon={<Plus />} onClick={openDialog}>
New publishable key
</Button>
</Shortcut>
<DialogContent>
<DialogHeader>
<DialogTitle>Create new publishable API key</DialogTitle>
<DialogDescription>
Publishable API keys are used to authorize requests to your project from the web, mobile
or desktop apps, CLIs or other public components of your application. They are safe to
be published online and embedded in code.
</DialogDescription>
</DialogHeader>
<DialogSectionSeparator />
<DialogSection className="flex flex-col gap-4">
<Form {...form}>
<form
ref={formRef}
className="flex flex-col gap-4"
id={FORM_ID}
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
key="name"
name="name"
control={form.control}
render={({ field }) => (
<FormItemLayout
label="Name"
description="A short name of lowercase alphanumeric characters and underscore, must start with letter or underscore."
>
<FormControl>
<Input {...field} />
</FormControl>
</FormItemLayout>
)}
/>
<FormField
key="description"
name="description"
control={form.control}
render={({ field }) => (
<FormItemLayout
label="Description"
description="Provide a description about what this key is used for."
>
<FormControl>
<Input {...field} placeholder="(Optional)" />
</FormControl>
</FormItemLayout>
)}
/>
</form>
</Form>
</DialogSection>
<DialogFooter>
<Shortcut
id={SHORTCUT_IDS.API_KEYS_CREATE_PUBLISHABLE}
onTrigger={() => formRef.current?.requestSubmit()}
options={{ enabled: visible === 'publishable' && !isCreatingAPIKey }}
side="top"
>
<Button form={FORM_ID} htmlType="submit" loading={isCreatingAPIKey}>
Create Publishable API key
</Button>
</Shortcut>
</DialogFooter>
</DialogContent>
</Dialog>
)
}