-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix: revert * feat: add basic view * feat: add secrets tab * Revert "fix: revert" This reverts commit 2a29f11. * chore: refactor * feat: use form state * feat: review comments
- Loading branch information
1 parent
fe0c55d
commit 1c0ba97
Showing
9 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { useState } from 'react' | ||
|
||
import { noop, useTranslationStore } from '@utils/viewUtils' | ||
|
||
import { Button, Drawer, Spacer } from '@harnessio/ui/components' | ||
import { CreateSecretPage, NewSecretFormFields, SecretsHeader, SecretType } from '@harnessio/ui/views' | ||
|
||
export const SecretsPage = () => { | ||
const [selectedType, setSelectedType] = useState<SecretType>(SecretType.New) | ||
|
||
const onSubmit = (data: NewSecretFormFields) => { | ||
console.log(data) | ||
} | ||
|
||
const renderContent = () => { | ||
switch (selectedType) { | ||
case SecretType.New: | ||
return ( | ||
<CreateSecretPage | ||
onFormSubmit={onSubmit} | ||
onFormCancel={noop} | ||
useTranslationStore={useTranslationStore} | ||
isLoading={false} | ||
apiError={null} | ||
/> | ||
) | ||
case SecretType.Existing: | ||
return <div>Existing Secret Content</div> | ||
default: | ||
return null | ||
} | ||
} | ||
|
||
return ( | ||
<Drawer.Root direction="right"> | ||
<Drawer.Trigger> | ||
<Button>Add Secret</Button> | ||
</Drawer.Trigger> | ||
<Drawer.Content> | ||
<Drawer.Header> | ||
<Drawer.Title className="text-3xl">Secrets</Drawer.Title> | ||
</Drawer.Header> | ||
<Spacer size={5} /> | ||
|
||
<SecretsHeader onChange={setSelectedType} /> | ||
<Spacer size={5} /> | ||
{renderContent()} | ||
</Drawer.Content> | ||
</Drawer.Root> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,3 +54,6 @@ export * from './labels' | |
|
||
// execution | ||
export * from './execution' | ||
|
||
// secrets | ||
export * from './secrets' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './new-secret/new-secret-form' | ||
export * from './secrets-header' |
148 changes: 148 additions & 0 deletions
148
packages/ui/src/views/secrets/new-secret/new-secret-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import { useForm, type SubmitHandler } from 'react-hook-form' | ||
|
||
import { | ||
Accordion, | ||
Alert, | ||
Button, | ||
ButtonGroup, | ||
ControlGroup, | ||
Fieldset, | ||
FormWrapper, | ||
Input, | ||
Spacer, | ||
Textarea | ||
} from '@/components' | ||
import { SandboxLayout, TranslationStore } from '@/views' | ||
import { zodResolver } from '@hookform/resolvers/zod' | ||
import { z } from 'zod' | ||
|
||
const newSecretformSchema = z.object({ | ||
name: z.string().min(1, { message: 'Please provide a name' }), | ||
value: z.string().min(1, { message: 'Please provide a value' }), | ||
description: z.string().optional(), | ||
tags: z.string().optional() | ||
}) | ||
|
||
export type NewSecretFormFields = z.infer<typeof newSecretformSchema> // Automatically generate a type from the schema | ||
|
||
interface CreateSecretPageProps { | ||
onFormSubmit: (data: NewSecretFormFields) => void | ||
onFormCancel: () => void | ||
useTranslationStore: () => TranslationStore | ||
isLoading: boolean | ||
apiError: string | null | ||
} | ||
|
||
export function CreateSecretPage({ | ||
onFormSubmit, | ||
onFormCancel, | ||
useTranslationStore, | ||
isLoading = false, | ||
apiError = null | ||
}: CreateSecretPageProps) { | ||
const { t: _t } = useTranslationStore() | ||
|
||
const { | ||
register, | ||
handleSubmit, | ||
reset, | ||
formState: { errors } | ||
} = useForm<NewSecretFormFields>({ | ||
resolver: zodResolver(newSecretformSchema), | ||
mode: 'onChange', | ||
defaultValues: { | ||
name: '', | ||
value: '', | ||
description: '', | ||
tags: '' | ||
} | ||
}) | ||
|
||
const onSubmit: SubmitHandler<NewSecretFormFields> = data => { | ||
onFormSubmit(data) | ||
reset() | ||
} | ||
|
||
const handleCancel = () => { | ||
onFormCancel() | ||
} | ||
|
||
return ( | ||
<SandboxLayout.Content className="px-0 pt-0 h-full"> | ||
<Spacer size={5} /> | ||
<FormWrapper className="flex h-full flex-col" onSubmit={handleSubmit(onSubmit)}> | ||
{/* NAME */} | ||
<Fieldset> | ||
<Input | ||
id="name" | ||
label="Secret Name" | ||
{...register('name')} | ||
placeholder="Enter secret name" | ||
size="md" | ||
error={errors.name?.message?.toString()} | ||
autoFocus | ||
/> | ||
|
||
<Input | ||
id="value" | ||
{...register('value')} | ||
type="password" | ||
label="Secret Value" | ||
placeholder="Add your secret value" | ||
size="md" | ||
error={errors.value?.message?.toString()} | ||
/> | ||
</Fieldset> | ||
<Accordion.Root type="single" collapsible> | ||
<Accordion.Item value="secret-details"> | ||
<Accordion.Trigger>Metadata</Accordion.Trigger> | ||
<Accordion.Content> | ||
<Fieldset className="rounded-md border-2 p-4"> | ||
{/* DESCRIPTION */} | ||
<Textarea | ||
id="description" | ||
{...register('description')} | ||
placeholder="Enter a description of this secret" | ||
label="Description" | ||
error={errors.description?.message?.toString()} | ||
optional | ||
/> | ||
{/* TAGS */} | ||
<Input | ||
id="tags" | ||
{...register('tags')} | ||
label="Tags" | ||
placeholder="Enter tags" | ||
size="md" | ||
error={errors.tags?.message?.toString()} | ||
optional | ||
/> | ||
</Fieldset> | ||
</Accordion.Content> | ||
</Accordion.Item> | ||
</Accordion.Root> | ||
|
||
{apiError && ( | ||
<Alert.Container variant="destructive" className="mb-8"> | ||
<Alert.Description>{apiError?.toString()}</Alert.Description> | ||
</Alert.Container> | ||
)} | ||
|
||
<div className="fixed bottom-0 left-0 right-0 bg-background-2 p-4 shadow-md"> | ||
<ControlGroup> | ||
<ButtonGroup className="flex flex-row justify-between"> | ||
<Button type="button" variant="outline" onClick={handleCancel}> | ||
Cancel | ||
</Button> | ||
<Button type="submit" disabled={isLoading}> | ||
{!isLoading ? 'Save' : 'Saving...'} | ||
</Button> | ||
</ButtonGroup> | ||
</ControlGroup> | ||
</div> | ||
|
||
<div className="pb-16"></div> | ||
</FormWrapper> | ||
</SandboxLayout.Content> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { useForm } from 'react-hook-form' | ||
|
||
import { Option, RadioButton, RadioGroup, StackedList } from '@/components' | ||
import { cn } from '@utils/cn' | ||
|
||
export enum SecretType { | ||
New = 'new', | ||
Existing = 'existing' | ||
} | ||
|
||
interface SecretTypeForm { | ||
type: SecretType | ||
} | ||
|
||
export const SecretsHeader = ({ onChange }: { onChange: (type: SecretType) => void }) => { | ||
const { watch, setValue } = useForm<SecretTypeForm>({ | ||
defaultValues: { | ||
type: SecretType.New | ||
} | ||
}) | ||
|
||
const selectedType = watch('type') | ||
|
||
const handleTypeChange = (value: SecretType) => { | ||
setValue('type', value) | ||
onChange(value) | ||
} | ||
|
||
return ( | ||
<RadioGroup value={selectedType} onValueChange={handleTypeChange} id="secret-type"> | ||
<div className="flex flex-col gap-2"> | ||
<Option | ||
id="new-secret" | ||
control={ | ||
<StackedList.Root className="overflow-hidden" borderBackground> | ||
<StackedList.Item | ||
className={cn('cursor-pointer !rounded px-5 py-3', { | ||
'!bg-background-4': selectedType === SecretType.New | ||
})} | ||
isHeader | ||
isLast | ||
actions={<RadioButton value={SecretType.New} />} | ||
onClick={() => handleTypeChange(SecretType.New)} | ||
> | ||
<StackedList.Field title="New Secret" description="Create a new secret." /> | ||
</StackedList.Item> | ||
</StackedList.Root> | ||
} | ||
/> | ||
<Option | ||
id="existing-secret" | ||
control={ | ||
<StackedList.Root className="overflow-hidden" borderBackground> | ||
<StackedList.Item | ||
className={cn('cursor-pointer !rounded px-5 py-3', { | ||
'!bg-background-4': selectedType === SecretType.Existing | ||
})} | ||
isHeader | ||
isLast | ||
actions={<RadioButton value={SecretType.Existing} />} | ||
onClick={() => handleTypeChange(SecretType.Existing)} | ||
> | ||
<StackedList.Field title="Existing Secret" description="Use an existing secret." /> | ||
</StackedList.Item> | ||
</StackedList.Root> | ||
} | ||
/> | ||
</div> | ||
</RadioGroup> | ||
) | ||
} |