Files
Ali Waseemandkemal 47dbbddc91 chore: fix secrets editor for functions to be text area/ support newlines (#46754)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Update to support text area for functions

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

* **New Features**
* Secret inputs now accept and preserve multi-line values and
auto-resize to fit content.
* Secret values can be masked/unmasked via a show/hide toggle with
tooltip; masking uses styled concealment.
* Per-secret controls refined: clearer row layout, dedicated remove
icon, and add/save controls moved to the card footer.

* **Tests**
* Added tests validating multi-line secret entry and that submitted
payloads include embedded newlines.
* Updated tests to assert masking/unmasking behavior via visual security
styling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: kemal <hello@kemal.earth>
2026-06-12 08:11:36 -06:00

178 lines
6.0 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 { useParams } from 'common'
import { Eye, EyeOff } from 'lucide-react'
import { useEffect, useState } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form'
import { useLatest } from 'react-use'
import { toast } from 'sonner'
import {
Button,
Form,
FormControl,
FormField,
Input,
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetSection,
SheetTitle,
Textarea,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import z from 'zod'
import { DiscardChangesConfirmationDialog } from '@/components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { useSecretsCreateMutation } from '@/data/secrets/secrets-create-mutation'
import { ProjectSecret } from '@/data/secrets/secrets-query'
import { useConfirmOnClose } from '@/hooks/ui/useConfirmOnClose'
const FORM_ID = 'edit-secret-sidepanel'
const FormSchema = z.object({
name: z.string().min(1, 'Please provide a name for your secret'),
value: z.string().min(1, 'Please provide a value for your secret'),
})
type FormSchemaType = z.infer<typeof FormSchema>
interface EditSecretSheetProps {
secret?: ProjectSecret
visible: boolean
onClose: () => void
}
export function EditSecretSheet({ secret, visible, onClose }: EditSecretSheetProps) {
const { ref: projectRef } = useParams()
const secretName = useLatest(secret?.name)
const [showSecretValue, setShowSecretValue] = useState(false)
const form = useForm<FormSchemaType>({
resolver: zodResolver(FormSchema),
})
const isValid = form.formState.isValid
const isDirty = form.formState.isDirty
const { mutate: updateSecret, isPending: isUpdating } = useSecretsCreateMutation({
onSuccess: (_, variables) => {
toast.success(`Successfully updated secret "${variables.secrets[0].name}"`)
onClose()
},
})
const onSubmit: SubmitHandler<FormSchemaType> = async ({ name, value }) => {
updateSecret({
projectRef,
secrets: [{ name, value }],
})
}
const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
checkIsDirty: () => isDirty,
onClose,
})
useEffect(() => {
if (visible) {
form.reset({ name: secretName.current ?? '', value: '' })
}
}, [form, secretName, visible])
return (
<Sheet open={visible} onOpenChange={handleOpenChange}>
<SheetContent size="default" className={'min-w-screen! lg:min-w-[600px]! flex flex-col'}>
<SheetHeader className="py-3 flex flex-row gap-3 items-center">
<SheetTitle>Edit secret</SheetTitle>
</SheetHeader>
<SheetSection className="h-full">
<Form {...form}>
<form
id={FORM_ID}
className="flex flex-col gap-y-4"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItemLayout label="Name" layout="horizontal">
<FormControl>
<Input
{...field}
readOnly
className="text-foreground-light! cursor-not-allowed"
/>
</FormControl>
</FormItemLayout>
)}
/>
<FormField
control={form.control}
name="value"
render={({ field }) => (
<FormItemLayout
label="Value"
layout="horizontal"
description="Secrets can’t be retrieved once saved. Enter a new value to overwrite the existing value."
>
<FormControl>
<div className="relative">
<Textarea
{...field}
rows={1}
ref={(el) => {
field.ref(el)
if (el) {
el.style.height = 'auto'
el.style.height = Math.max(40, el.scrollHeight) + 'px'
}
}}
placeholder="my-secret-value"
data-1p-ignore
data-lpignore="true"
data-form-type="other"
data-bwignore
className="min-h-0 resize-none"
style={
{
WebkitTextSecurity: showSecretValue ? undefined : 'disc',
} as React.CSSProperties
}
onChange={(e) => {
field.onChange(e)
e.currentTarget.style.height = 'auto'
e.currentTarget.style.height =
Math.max(40, e.currentTarget.scrollHeight) + 'px'
}}
/>
<Button
type="text"
className="absolute right-1 top-1 px-1"
icon={showSecretValue ? <EyeOff /> : <Eye />}
onClick={() => setShowSecretValue(!showSecretValue)}
/>
</div>
</FormControl>
</FormItemLayout>
)}
/>
</form>
</Form>
</SheetSection>
<SheetFooter>
<Button disabled={isUpdating} type="default" onClick={confirmOnClose}>
Cancel
</Button>
<Button form={FORM_ID} htmlType="submit" disabled={!isValid} loading={isUpdating}>
Save
</Button>
</SheetFooter>
</SheetContent>
<DiscardChangesConfirmationDialog {...modalProps} />
</Sheet>
)
}