1- import { useForm } from "@conform-to/react" ;
1+ import { conform , useForm } from "@conform-to/react" ;
22import { parse } from "@conform-to/zod" ;
33import {
44 BookOpenIcon ,
@@ -56,6 +56,7 @@ import {
5656 type EnvironmentVariableWithSetValues ,
5757 EnvironmentVariablesPresenter ,
5858} from "~/presenters/v3/EnvironmentVariablesPresenter.server" ;
59+ import { logger } from "~/services/logger.server" ;
5960import { requireUserId } from "~/services/session.server" ;
6061import { cn } from "~/utils/cn" ;
6162import {
@@ -69,6 +70,7 @@ import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/enviro
6970import {
7071 DeleteEnvironmentVariable ,
7172 EditEnvironmentVariable ,
73+ EditEnvironmentVariableValue ,
7274} from "~/v3/environmentVariables/repository" ;
7375
7476export const meta : MetaFunction = ( ) => {
@@ -105,7 +107,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
105107} ;
106108
107109const schema = z . discriminatedUnion ( "action" , [
108- z . object ( { action : z . literal ( "edit" ) , key : z . string ( ) , ... EditEnvironmentVariable . shape } ) ,
110+ z . object ( { action : z . literal ( "edit" ) , ... EditEnvironmentVariableValue . shape } ) ,
109111 z . object ( { action : z . literal ( "delete" ) , key : z . string ( ) , ...DeleteEnvironmentVariable . shape } ) ,
110112] ) ;
111113
@@ -146,8 +148,11 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
146148
147149 switch ( submission . value . action ) {
148150 case "edit" : {
151+ logger . debug ( "ENVVARS edit" , { submission : submission . value } ) ;
149152 const repository = new EnvironmentVariablesRepository ( prisma ) ;
150- const result = await repository . edit ( project . id , submission . value ) ;
153+ const result = await repository . editValue ( project . id , submission . value ) ;
154+
155+ logger . debug ( "ENVVARS edit result" , { result } ) ;
151156
152157 if ( ! result . success ) {
153158 submission . error . key = result . error ;
@@ -237,28 +242,37 @@ export default function Page() {
237242 < TableHeaderCell > Key</ TableHeaderCell >
238243 < TableHeaderCell > Value</ TableHeaderCell >
239244 < TableHeaderCell > Environment</ TableHeaderCell >
240- < TableHeaderCell hiddenLabel > Actions</ TableHeaderCell >
245+ < TableHeaderCell hiddenLabel className = "pl-24" >
246+ Actions
247+ </ TableHeaderCell >
241248 </ TableRow >
242249 </ TableHeader >
243250 < TableBody >
244251 { environmentVariables . length > 0 ? (
245252 environmentVariables . map ( ( variable ) => (
246- < TableRow key = { variable . id } >
253+ < TableRow key = { ` ${ variable . id } - ${ variable . environment . id } ` } >
247254 < TableCell >
248255 < CopyableText value = { variable . key } className = "font-mono" />
249256 </ TableCell >
250257 < TableCell >
251258 { variable . isSecret ? (
252259 < SimpleTooltip
253- button = { < LockClosedIcon className = "size-4 text-charcoal-400" /> }
260+ button = {
261+ < div className = "flex items-center gap-x-1.5" >
262+ < LockClosedIcon className = "size-4 text-text-dimmed" />
263+ < span className = "text-sm text-text-dimmed" > Secret</ span >
264+ </ div >
265+ }
254266 content = "This variable is secret and cannot be revealed."
255267 />
256268 ) : (
257269 < ClipboardField
258270 className = "-ml-2"
259271 secure = { ! revealAll }
260272 value = { variable . value }
261- variant = { "secondary/small" }
273+ variant = { "tertiary/small" }
274+ iconButton
275+ fullWidth = { false }
262276 />
263277 ) }
264278 </ TableCell >
@@ -268,13 +282,9 @@ export default function Page() {
268282 </ TableCell >
269283 < TableCellMenu
270284 isSticky
271- popoverContent = {
285+ hiddenButtons = {
272286 < >
273- { /* <EditEnvironmentVariablePanel
274- environments={environments}
275- variable={variable}
276- revealAll={revealAll}
277- /> */ }
287+ < EditEnvironmentVariablePanel variable = { variable } revealAll = { revealAll } />
278288 < DeleteEnvironmentVariableButton variable = { variable } />
279289 </ >
280290 }
@@ -283,7 +293,7 @@ export default function Page() {
283293 ) )
284294 ) : (
285295 < TableRow >
286- < TableCell colSpan = { environments . length + 2 } >
296+ < TableCell colSpan = { 4 } >
287297 < div className = "flex flex-col items-center justify-center gap-y-4 py-8" >
288298 < Header2 > You haven't set any environment variables yet.</ Header2 >
289299 < LinkButton
@@ -314,132 +324,102 @@ export default function Page() {
314324 ) ;
315325}
316326
317- // function EditEnvironmentVariablePanel({
318- // variable,
319- // environments,
320- // revealAll,
321- // }: {
322- // variable: EnvironmentVariableWithSetValues;
323- // environments: Pick<RuntimeEnvironment, "id" | "type">[];
324- // revealAll: boolean;
325- // }) {
326- // const [reveal, setReveal] = useState(revealAll);
327-
328- // const [isOpen, setIsOpen] = useState(false);
329- // const lastSubmission = useActionData();
330- // const navigation = useNavigation();
331-
332- // const hiddenValues = Object.values(variable.values).filter(
333- // (value) => !environments.map((e) => e.id).includes(value.environment.id)
334- // );
335-
336- // const isLoading =
337- // navigation.state !== "idle" &&
338- // navigation.formMethod === "post" &&
339- // navigation.formData?.get("action") === "edit";
340-
341- // const [form, { id }] = useForm({
342- // id: "edit-environment-variable",
343- // // TODO: type this
344- // lastSubmission: lastSubmission as any,
345- // onValidate({ formData }) {
346- // return parse(formData, { schema });
347- // },
348- // shouldRevalidate: "onSubmit",
349- // });
350-
351- // return (
352- // <Dialog open={isOpen} onOpenChange={setIsOpen}>
353- // <DialogTrigger asChild>
354- // <Button variant="small-menu-item" LeadingIcon={PencilSquareIcon} fullWidth textAlignLeft>
355- // Edit
356- // </Button>
357- // </DialogTrigger>
358- // <DialogContent>
359- // <DialogHeader>Edit {variable.key}</DialogHeader>
360- // <Form method="post" {...form.props}>
361- // <input type="hidden" name="action" value="edit" />
362- // <input type="hidden" name="id" value={variable.id} />
363- // <input type="hidden" name="key" value={variable.key} />
364- // {hiddenValues.map((value, index) => (
365- // <Fragment key={index}>
366- // <input
367- // type="hidden"
368- // name={`values[${index}].environmentId`}
369- // value={value.environment.id}
370- // />
371- // <input type="hidden" name={`values[${index}].value`} value={value.value} />
372- // </Fragment>
373- // ))}
374- // <FormError id={id.errorId}>{id.error}</FormError>
375- // <Fieldset>
376- // <InputGroup fullWidth className="mb-5 mt-2">
377- // <Label>Key</Label>
378- // <InlineCode variant="base" className="pl-1.5">
379- // {variable.key}
380- // </InlineCode>
381- // </InputGroup>
382- // </Fieldset>
383- // <Fieldset>
384- // <InputGroup fullWidth>
385- // <div className="flex justify-between gap-1">
386- // <Label>Values</Label>
387- // <Switch
388- // variant="small"
389- // label="Reveal"
390- // checked={reveal}
391- // onCheckedChange={(e) => setReveal(e.valueOf())}
392- // />
393- // </div>
394- // <div className="grid grid-cols-[auto_1fr] gap-x-2 gap-y-2">
395- // {environments.map((environment, index) => {
396- // const value = variable.values[environment.id]?.value;
397- // index += hiddenValues.length;
398- // return (
399- // <Fragment key={environment.id}>
400- // <input
401- // type="hidden"
402- // name={`values[${index}].environmentId`}
403- // value={environment.id}
404- // />
405- // <label
406- // className="flex items-center justify-end"
407- // htmlFor={`values[${index}].value` }
408- // >
409- // <EnvironmentCombo environment={environment} className="text-sm" />
410- // </label>
411- // <Input
412- // name={`values[${index}].value` }
413- // placeholder="Not set"
414- // defaultValue={value}
415- // type={reveal ? "text" : "password"}
416- // />
417- // </Fragment>
418- // );
419- // }) }
420- // </div>
421- // </InputGroup>
422-
423- // <FormError>{form.error}</FormError>
424-
425- // <FormButtons
426- // confirmButton={
427- // <Button type="submit" variant="primary/medium" disabled={isLoading}>
428- // {isLoading ? "Saving…" : "Save"}
429- // </Button>
430- // }
431- // cancelButton={
432- // <Button onClick={() => setIsOpen(false)} variant="tertiary/medium" type="button">
433- // Cancel
434- // </Button>
435- // }
436- // />
437- // </Fieldset>
438- // </Form>
439- // </DialogContent>
440- // </Dialog>
441- // );
442- // }
327+ function EditEnvironmentVariablePanel ( {
328+ variable,
329+ revealAll,
330+ } : {
331+ variable : EnvironmentVariableWithSetValues ;
332+ revealAll : boolean ;
333+ } ) {
334+ const [ reveal , setReveal ] = useState ( revealAll ) ;
335+
336+ const [ isOpen , setIsOpen ] = useState ( false ) ;
337+ const lastSubmission = useActionData ( ) ;
338+ const navigation = useNavigation ( ) ;
339+
340+ const isLoading =
341+ navigation . state !== "idle" &&
342+ navigation . formMethod === "post" &&
343+ navigation . formData ?. get ( "action" ) === "edit" ;
344+
345+ const [ form , { id, environmentId, value } ] = useForm ( {
346+ id : "edit-environment-variable" ,
347+ // TODO: type this
348+ lastSubmission : lastSubmission as any ,
349+ onValidate ( { formData } ) {
350+ return parse ( formData , { schema } ) ;
351+ } ,
352+ shouldRevalidate : "onSubmit" ,
353+ } ) ;
354+
355+ console . log ( "edit form" , { form, variable, id, environmentId, value } ) ;
356+
357+ return (
358+ < Dialog open = { isOpen } onOpenChange = { setIsOpen } >
359+ < DialogTrigger asChild >
360+ < Button variant = "small-menu-item" LeadingIcon = { PencilSquareIcon } fullWidth textAlignLeft >
361+ Edit
362+ </ Button >
363+ </ DialogTrigger >
364+ < DialogContent >
365+ < DialogHeader > Edit environment variable</ DialogHeader >
366+ < Form method = "post" { ...form . props } >
367+ < input type = "hidden" name = "action" value = "edit" />
368+ < input { ...conform . input ( id , { type : "hidden" } ) } value = { variable . id } />
369+ < input
370+ { ...conform . input ( environmentId , { type : "hidden" } ) }
371+ value = { variable . environment . id }
372+ />
373+ < FormError id = { id . errorId } > { id . error } </ FormError >
374+ < FormError id = { environmentId . errorId } > { environmentId . error } </ FormError >
375+ < Fieldset >
376+ < InputGroup fullWidth className = "mb-5 mt-2" >
377+ < Label > Key</ Label >
378+ < InlineCode variant = "base" className = "pl-1.5" >
379+ { variable . key }
380+ </ InlineCode >
381+ </ InputGroup >
382+ < EnvironmentCombo environment = { variable . environment } className = "text-sm" />
383+
384+ < div className = "flex justify-between gap-1" >
385+ < Switch
386+ variant = "small"
387+ label = "Reveal"
388+ checked = { reveal }
389+ onCheckedChange = { ( e ) => setReveal ( e . valueOf ( ) ) }
390+ />
391+ </ div >
392+ < InputGroup fullWidth >
393+ < Label > Value</ Label >
394+ < Input
395+ { ...conform . input ( value , { type : "text" } ) }
396+ placeholder = { variable . isSecret ? "Set new secret value" : "Not set" }
397+ defaultValue = { variable . value }
398+ type = { reveal ? "text" : "password" }
399+ />
400+ < FormError id = { value . errorId } > { value . error } </ FormError >
401+ </ InputGroup >
402+
403+ < FormError > { form . error } </ FormError >
404+
405+ < FormButtons
406+ confirmButton = {
407+ < Button type = "submit" variant = "primary/medium" disabled = { isLoading } >
408+ { isLoading ? "Saving…" : "Save" }
409+ </ Button >
410+ }
411+ cancelButton = {
412+ < Button onClick = { ( ) => setIsOpen ( false ) } variant = "tertiary/medium" type = "button" >
413+ Cancel
414+ </ Button >
415+ }
416+ />
417+ </ Fieldset >
418+ </ Form >
419+ </ DialogContent >
420+ </ Dialog >
421+ ) ;
422+ }
443423
444424function DeleteEnvironmentVariableButton ( {
445425 variable,
0 commit comments