@shipfox/client-secrets 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +4 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.turbo/turbo-build.log +2 -0
  5. package/.turbo/turbo-type$colon$emit.log +1 -0
  6. package/.turbo/turbo-type.log +1 -0
  7. package/CHANGELOG.md +102 -0
  8. package/LICENSE +21 -0
  9. package/dist/components/copy-key.d.ts +8 -0
  10. package/dist/components/copy-key.d.ts.map +1 -0
  11. package/dist/components/copy-key.js +21 -0
  12. package/dist/components/copy-key.js.map +1 -0
  13. package/dist/components/delete-entry-dialog.d.ts +9 -0
  14. package/dist/components/delete-entry-dialog.d.ts.map +1 -0
  15. package/dist/components/delete-entry-dialog.js +66 -0
  16. package/dist/components/delete-entry-dialog.js.map +1 -0
  17. package/dist/components/form-errors.d.ts +17 -0
  18. package/dist/components/form-errors.d.ts.map +1 -0
  19. package/dist/components/form-errors.js +81 -0
  20. package/dist/components/form-errors.js.map +1 -0
  21. package/dist/components/form-shell.d.ts +14 -0
  22. package/dist/components/form-shell.d.ts.map +1 -0
  23. package/dist/components/form-shell.js +28 -0
  24. package/dist/components/form-shell.js.map +1 -0
  25. package/dist/components/secret-form.d.ts +10 -0
  26. package/dist/components/secret-form.d.ts.map +1 -0
  27. package/dist/components/secret-form.js +176 -0
  28. package/dist/components/secret-form.js.map +1 -0
  29. package/dist/components/secret-form.stories.d.ts +9 -0
  30. package/dist/components/secret-form.stories.d.ts.map +1 -0
  31. package/dist/components/secret-form.stories.js +60 -0
  32. package/dist/components/secret-form.stories.js.map +1 -0
  33. package/dist/components/store-key.d.ts +20 -0
  34. package/dist/components/store-key.d.ts.map +1 -0
  35. package/dist/components/store-key.js +25 -0
  36. package/dist/components/store-key.js.map +1 -0
  37. package/dist/components/store-section-shell.d.ts +10 -0
  38. package/dist/components/store-section-shell.d.ts.map +1 -0
  39. package/dist/components/store-section-shell.js +40 -0
  40. package/dist/components/store-section-shell.js.map +1 -0
  41. package/dist/components/variable-form.d.ts +12 -0
  42. package/dist/components/variable-form.d.ts.map +1 -0
  43. package/dist/components/variable-form.js +186 -0
  44. package/dist/components/variable-form.js.map +1 -0
  45. package/dist/components/variable-form.stories.d.ts +9 -0
  46. package/dist/components/variable-form.stories.d.ts.map +1 -0
  47. package/dist/components/variable-form.stories.js +61 -0
  48. package/dist/components/variable-form.stories.js.map +1 -0
  49. package/dist/components/workspace-secrets-section.d.ts +4 -0
  50. package/dist/components/workspace-secrets-section.d.ts.map +1 -0
  51. package/dist/components/workspace-secrets-section.js +251 -0
  52. package/dist/components/workspace-secrets-section.js.map +1 -0
  53. package/dist/components/workspace-secrets-section.stories.d.ts +13 -0
  54. package/dist/components/workspace-secrets-section.stories.d.ts.map +1 -0
  55. package/dist/components/workspace-secrets-section.stories.js +140 -0
  56. package/dist/components/workspace-secrets-section.stories.js.map +1 -0
  57. package/dist/components/workspace-variables-section.d.ts +4 -0
  58. package/dist/components/workspace-variables-section.d.ts.map +1 -0
  59. package/dist/components/workspace-variables-section.js +255 -0
  60. package/dist/components/workspace-variables-section.js.map +1 -0
  61. package/dist/components/workspace-variables-section.stories.d.ts +11 -0
  62. package/dist/components/workspace-variables-section.stories.d.ts.map +1 -0
  63. package/dist/components/workspace-variables-section.stories.js +127 -0
  64. package/dist/components/workspace-variables-section.stories.js.map +1 -0
  65. package/dist/hooks/api/create-store-api.d.ts +56 -0
  66. package/dist/hooks/api/create-store-api.d.ts.map +1 -0
  67. package/dist/hooks/api/create-store-api.js +95 -0
  68. package/dist/hooks/api/create-store-api.js.map +1 -0
  69. package/dist/hooks/api/secrets.d.ts +61 -0
  70. package/dist/hooks/api/secrets.d.ts.map +1 -0
  71. package/dist/hooks/api/secrets.js +15 -0
  72. package/dist/hooks/api/secrets.js.map +1 -0
  73. package/dist/hooks/api/variables.d.ts +88 -0
  74. package/dist/hooks/api/variables.d.ts.map +1 -0
  75. package/dist/hooks/api/variables.js +52 -0
  76. package/dist/hooks/api/variables.js.map +1 -0
  77. package/dist/index.d.ts +6 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +7 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/tsconfig.test.tsbuildinfo +1 -0
  82. package/package.json +89 -0
  83. package/src/components/copy-key.test.ts +44 -0
  84. package/src/components/copy-key.ts +21 -0
  85. package/src/components/delete-entry-dialog.tsx +57 -0
  86. package/src/components/form-errors.test.ts +67 -0
  87. package/src/components/form-errors.ts +59 -0
  88. package/src/components/form-shell.tsx +31 -0
  89. package/src/components/secret-form.stories.tsx +58 -0
  90. package/src/components/secret-form.test.tsx +37 -0
  91. package/src/components/secret-form.tsx +170 -0
  92. package/src/components/store-key.test.ts +53 -0
  93. package/src/components/store-key.ts +36 -0
  94. package/src/components/store-section-shell.tsx +32 -0
  95. package/src/components/variable-form.stories.tsx +58 -0
  96. package/src/components/variable-form.test.tsx +60 -0
  97. package/src/components/variable-form.tsx +182 -0
  98. package/src/components/workspace-secrets-section.stories.tsx +128 -0
  99. package/src/components/workspace-secrets-section.tsx +231 -0
  100. package/src/components/workspace-variables-section.stories.tsx +121 -0
  101. package/src/components/workspace-variables-section.tsx +238 -0
  102. package/src/hooks/api/create-store-api.ts +114 -0
  103. package/src/hooks/api/secrets.test.ts +157 -0
  104. package/src/hooks/api/secrets.ts +26 -0
  105. package/src/hooks/api/variables.ts +61 -0
  106. package/src/index.ts +5 -0
  107. package/test/fixtures/secrets.ts +59 -0
  108. package/test/setup.ts +3 -0
  109. package/tsconfig.build.json +9 -0
  110. package/tsconfig.build.tsbuildinfo +1 -0
  111. package/tsconfig.json +3 -0
  112. package/tsconfig.test.json +8 -0
  113. package/vercel.json +8 -0
  114. package/vitest.config.ts +75 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/variable-form.tsx"],"sourcesContent":["import {isSensitiveSecretName} from '@shipfox/api-secrets-dto';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {\n FormField,\n FormFieldInput,\n FormFieldTextarea,\n fieldError,\n} from '@shipfox/react-ui/form-field';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useEffect, useRef, useState} from 'react';\nimport {usePutVariableMutation, useVariableQuery} from '#hooks/api/variables.js';\nimport {secretsErrorToFormError} from './form-errors.js';\nimport {FormBody, FormFooter} from './form-shell.js';\nimport {STORE_KEY_HELP, validateNewStoreKey} from './store-key.js';\n\nexport const VARIABLE_FORM_ID = 'variable-form';\n\nexport function VariableForm({\n workspaceId,\n mode,\n existingKey,\n existingValue,\n existingValueTruncated = false,\n reservedKeys = [],\n onSaved,\n onCancel,\n}: {\n workspaceId: string;\n mode: 'create' | 'edit';\n existingKey?: string | undefined;\n existingValue?: string | undefined;\n existingValueTruncated?: boolean | undefined;\n reservedKeys?: readonly string[] | undefined;\n onSaved: () => void;\n onCancel: () => void;\n}) {\n const putVariable = usePutVariableMutation();\n const [formError, setFormError] = useState<string | undefined>();\n\n // The list value is only a preview, so a truncated variable must load its full\n // value before editing to avoid saving the truncated preview back.\n const needsFullValue = mode === 'edit' && existingValueTruncated;\n const fullValueQuery = useVariableQuery(workspaceId, needsFullValue ? existingKey : undefined);\n\n const form = useForm({\n defaultValues: {key: existingKey ?? '', value: existingValue ?? ''},\n onSubmit: async ({value}) => {\n setFormError(undefined);\n try {\n await putVariable.mutateAsync({workspaceId, key: value.key, body: {value: value.value}});\n onSaved();\n } catch (error) {\n const mapped = secretsErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (prev) => ({\n ...prev,\n errorMap: {...prev.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n },\n });\n\n const populatedRef = useRef(false);\n useEffect(() => {\n if (needsFullValue && fullValueQuery.data && !populatedRef.current) {\n populatedRef.current = true;\n form.setFieldValue('value', fullValueQuery.data.value);\n }\n }, [needsFullValue, fullValueQuery.data, form]);\n\n // The full value must be loaded before a save is allowed, otherwise submitting would\n // overwrite the stored value with the truncated preview. Block until it arrives —\n // whether the fetch is still pending or has failed.\n const awaitingFullValue = needsFullValue && fullValueQuery.data === undefined;\n const loadingFullValue = needsFullValue && fullValueQuery.isPending;\n\n return (\n <>\n <FormBody>\n <form\n id={VARIABLE_FORM_ID}\n className=\"flex w-full flex-col gap-16\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Field\n name=\"key\"\n validators={{\n onBlur: ({value}) =>\n validateNewStoreKey(value, {mode, reservedKeys, kind: 'variable'}),\n onSubmit: ({value}) =>\n validateNewStoreKey(value, {mode, reservedKeys, kind: 'variable'}),\n }}\n >\n {(field) => (\n <FormField\n label=\"Name\"\n id=\"variable-key\"\n description={STORE_KEY_HELP}\n error={fieldError(field)}\n >\n <FormFieldInput\n className=\"font-code\"\n autoComplete=\"off\"\n spellCheck={false}\n disabled={mode === 'edit'}\n placeholder=\"LOG_LEVEL\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value.toUpperCase())}\n onBlur={field.handleBlur}\n />\n {isSensitiveSecretName(field.state.value) ? (\n <Callout role=\"alert\" type=\"warning\">\n <Text size=\"sm\" aria-live=\"polite\">\n This looks like it may be sensitive. Variables are stored in plaintext and are\n not redacted from logs. Use a Secret if this value contains private data.\n </Text>\n </Callout>\n ) : null}\n </FormField>\n )}\n </form.Field>\n\n <form.Field name=\"value\">\n {(field) => (\n <FormField\n label=\"Value\"\n id=\"variable-value\"\n error={fieldError(field)}\n description={loadingFullValue ? 'Loading the current value…' : undefined}\n >\n <FormFieldTextarea\n className=\"font-code\"\n autoComplete=\"off\"\n spellCheck={false}\n rows={3}\n disabled={awaitingFullValue}\n placeholder=\"debug\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n </form>\n {awaitingFullValue && fullValueQuery.isError ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">Could not load the current value. Close and try again.</Text>\n </Callout>\n ) : null}\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{formError}</Text>\n </Callout>\n ) : null}\n </FormBody>\n <FormFooter>\n <Button variant=\"secondary\" type=\"button\" onClick={onCancel}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={VARIABLE_FORM_ID}\n isLoading={putVariable.isPending}\n disabled={awaitingFullValue}\n >\n {mode === 'edit' ? 'Update variable' : 'Create variable'}\n </Button>\n </FormFooter>\n </>\n );\n}\n"],"names":["isSensitiveSecretName","Button","Callout","FormField","FormFieldInput","FormFieldTextarea","fieldError","Text","useForm","useEffect","useRef","useState","usePutVariableMutation","useVariableQuery","secretsErrorToFormError","FormBody","FormFooter","STORE_KEY_HELP","validateNewStoreKey","VARIABLE_FORM_ID","VariableForm","workspaceId","mode","existingKey","existingValue","existingValueTruncated","reservedKeys","onSaved","onCancel","putVariable","formError","setFormError","needsFullValue","fullValueQuery","undefined","form","defaultValues","key","value","onSubmit","mutateAsync","body","error","mapped","kind","setFieldMeta","field","prev","errorMap","onServer","message","populatedRef","data","current","setFieldValue","awaitingFullValue","loadingFullValue","isPending","id","className","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","name","validators","onBlur","label","description","autoComplete","spellCheck","disabled","placeholder","state","onChange","handleChange","target","toUpperCase","handleBlur","role","type","size","aria-live","rows","isError","variant","onClick","isLoading"],"mappings":";AAAA,SAAQA,qBAAqB,QAAO,2BAA2B;AAC/D,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,OAAO,QAAO,4BAA4B;AAClD,SACEC,SAAS,EACTC,cAAc,EACdC,iBAAiB,EACjBC,UAAU,QACL,+BAA+B;AACtC,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClD,SAAQC,sBAAsB,EAAEC,gBAAgB,QAAO,0BAA0B;AACjF,SAAQC,uBAAuB,QAAO,mBAAmB;AACzD,SAAQC,QAAQ,EAAEC,UAAU,QAAO,kBAAkB;AACrD,SAAQC,cAAc,EAAEC,mBAAmB,QAAO,iBAAiB;AAEnE,OAAO,MAAMC,mBAAmB,gBAAgB;AAEhD,OAAO,SAASC,aAAa,EAC3BC,WAAW,EACXC,IAAI,EACJC,WAAW,EACXC,aAAa,EACbC,yBAAyB,KAAK,EAC9BC,eAAe,EAAE,EACjBC,OAAO,EACPC,QAAQ,EAUT;IACC,MAAMC,cAAcjB;IACpB,MAAM,CAACkB,WAAWC,aAAa,GAAGpB;IAElC,+EAA+E;IAC/E,mEAAmE;IACnE,MAAMqB,iBAAiBV,SAAS,UAAUG;IAC1C,MAAMQ,iBAAiBpB,iBAAiBQ,aAAaW,iBAAiBT,cAAcW;IAEpF,MAAMC,OAAO3B,QAAQ;QACnB4B,eAAe;YAACC,KAAKd,eAAe;YAAIe,OAAOd,iBAAiB;QAAE;QAClEe,UAAU,OAAO,EAACD,KAAK,EAAC;YACtBP,aAAaG;YACb,IAAI;gBACF,MAAML,YAAYW,WAAW,CAAC;oBAACnB;oBAAagB,KAAKC,MAAMD,GAAG;oBAAEI,MAAM;wBAACH,OAAOA,MAAMA,KAAK;oBAAA;gBAAC;gBACtFX;YACF,EAAE,OAAOe,OAAO;gBACd,MAAMC,SAAS7B,wBAAwB4B;gBACvC,IAAIC,OAAOC,IAAI,KAAK,SAAS;oBAC3BT,KAAKU,YAAY,CAACF,OAAOG,KAAK,EAAE,CAACC,OAAU,CAAA;4BACzC,GAAGA,IAAI;4BACPC,UAAU;gCAAC,GAAGD,KAAKC,QAAQ;gCAAEC,UAAUN,OAAOO,OAAO;4BAAA;wBACvD,CAAA;gBACF,OAAO;oBACLnB,aAAaY,OAAOO,OAAO;gBAC7B;YACF;QACF;IACF;IAEA,MAAMC,eAAezC,OAAO;IAC5BD,UAAU;QACR,IAAIuB,kBAAkBC,eAAemB,IAAI,IAAI,CAACD,aAAaE,OAAO,EAAE;YAClEF,aAAaE,OAAO,GAAG;YACvBlB,KAAKmB,aAAa,CAAC,SAASrB,eAAemB,IAAI,CAACd,KAAK;QACvD;IACF,GAAG;QAACN;QAAgBC,eAAemB,IAAI;QAAEjB;KAAK;IAE9C,qFAAqF;IACrF,kFAAkF;IAClF,oDAAoD;IACpD,MAAMoB,oBAAoBvB,kBAAkBC,eAAemB,IAAI,KAAKlB;IACpE,MAAMsB,mBAAmBxB,kBAAkBC,eAAewB,SAAS;IAEnE,qBACE;;0BACE,MAAC1C;;kCACC,MAACoB;wBACCuB,IAAIvC;wBACJwC,WAAU;wBACVC,UAAU;wBACVrB,UAAU,CAACsB;4BACTA,MAAMC,cAAc;4BACpBD,MAAME,eAAe;4BACrB,KAAK5B,KAAK6B,YAAY;wBACxB;;0CAEA,KAAC7B,KAAK8B,KAAK;gCACTC,MAAK;gCACLC,YAAY;oCACVC,QAAQ,CAAC,EAAC9B,KAAK,EAAC,GACdpB,oBAAoBoB,OAAO;4CAAChB;4CAAMI;4CAAckB,MAAM;wCAAU;oCAClEL,UAAU,CAAC,EAACD,KAAK,EAAC,GAChBpB,oBAAoBoB,OAAO;4CAAChB;4CAAMI;4CAAckB,MAAM;wCAAU;gCACpE;0CAEC,CAACE,sBACA,MAAC3C;wCACCkE,OAAM;wCACNX,IAAG;wCACHY,aAAarD;wCACbyB,OAAOpC,WAAWwC;;0DAElB,KAAC1C;gDACCuD,WAAU;gDACVY,cAAa;gDACbC,YAAY;gDACZC,UAAUnD,SAAS;gDACnBoD,aAAY;gDACZpC,OAAOQ,MAAM6B,KAAK,CAACrC,KAAK;gDACxBsC,UAAU,CAACf,QAAUf,MAAM+B,YAAY,CAAChB,MAAMiB,MAAM,CAACxC,KAAK,CAACyC,WAAW;gDACtEX,QAAQtB,MAAMkC,UAAU;;4CAEzBhF,sBAAsB8C,MAAM6B,KAAK,CAACrC,KAAK,kBACtC,KAACpC;gDAAQ+E,MAAK;gDAAQC,MAAK;0DACzB,cAAA,KAAC3E;oDAAK4E,MAAK;oDAAKC,aAAU;8DAAS;;iDAKnC;;;;0CAKV,KAACjD,KAAK8B,KAAK;gCAACC,MAAK;0CACd,CAACpB,sBACA,KAAC3C;wCACCkE,OAAM;wCACNX,IAAG;wCACHhB,OAAOpC,WAAWwC;wCAClBwB,aAAad,mBAAmB,+BAA+BtB;kDAE/D,cAAA,KAAC7B;4CACCsD,WAAU;4CACVY,cAAa;4CACbC,YAAY;4CACZa,MAAM;4CACNZ,UAAUlB;4CACVmB,aAAY;4CACZpC,OAAOQ,MAAM6B,KAAK,CAACrC,KAAK;4CACxBsC,UAAU,CAACf,QAAUf,MAAM+B,YAAY,CAAChB,MAAMiB,MAAM,CAACxC,KAAK;4CAC1D8B,QAAQtB,MAAMkC,UAAU;;;;;;oBAMjCzB,qBAAqBtB,eAAeqD,OAAO,iBAC1C,KAACpF;wBAAQ+E,MAAK;wBAAQC,MAAK;kCACzB,cAAA,KAAC3E;4BAAK4E,MAAK;sCAAK;;yBAEhB;oBACHrD,0BACC,KAAC5B;wBAAQ+E,MAAK;wBAAQC,MAAK;kCACzB,cAAA,KAAC3E;4BAAK4E,MAAK;sCAAMrD;;yBAEjB;;;0BAEN,MAACd;;kCACC,KAACf;wBAAOsF,SAAQ;wBAAYL,MAAK;wBAASM,SAAS5D;kCAAU;;kCAG7D,KAAC3B;wBACCiF,MAAK;wBACL/C,MAAMhB;wBACNsE,WAAW5D,YAAY4B,SAAS;wBAChCgB,UAAUlB;kCAETjC,SAAS,SAAS,oBAAoB;;;;;;AAKjD"}
@@ -0,0 +1,9 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { VariableForm } from './variable-form.js';
3
+ declare const meta: Meta<typeof VariableForm>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof VariableForm>;
6
+ export declare const Playground: Story;
7
+ export declare const Edit: Story;
8
+ export declare const SensitiveNameWarning: Story;
9
+ //# sourceMappingURL=variable-form.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variable-form.stories.d.ts","sourceRoot":"","sources":["../../src/components/variable-form.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,IAAI,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAKrD,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAmBhD,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAgBnC,CAAC;AACF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3C,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,IAAI,EAAE,KAElB,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,KAKlC,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Toaster } from '@shipfox/react-ui/toast';
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { useMemo } from 'react';
5
+ import { userEvent, within } from 'storybook/test';
6
+ import { VariableForm } from './variable-form.js';
7
+ const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
8
+ function Wrapper({ children }) {
9
+ const queryClient = useMemo(()=>new QueryClient({
10
+ defaultOptions: {
11
+ queries: {
12
+ retry: false
13
+ },
14
+ mutations: {
15
+ retry: false
16
+ }
17
+ }
18
+ }), []);
19
+ return /*#__PURE__*/ _jsxs(QueryClientProvider, {
20
+ client: queryClient,
21
+ children: [
22
+ /*#__PURE__*/ _jsx("div", {
23
+ className: "w-[520px] overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base",
24
+ children: children
25
+ }),
26
+ /*#__PURE__*/ _jsx(Toaster, {})
27
+ ]
28
+ });
29
+ }
30
+ const meta = {
31
+ title: 'Secrets/VariableForm',
32
+ component: VariableForm,
33
+ args: {
34
+ workspaceId: WORKSPACE_ID,
35
+ mode: 'create',
36
+ onSaved: ()=>undefined,
37
+ onCancel: ()=>undefined
38
+ },
39
+ decorators: [
40
+ (Story)=>/*#__PURE__*/ _jsx(Wrapper, {
41
+ children: /*#__PURE__*/ _jsx(Story, {})
42
+ })
43
+ ]
44
+ };
45
+ export default meta;
46
+ export const Playground = {};
47
+ export const Edit = {
48
+ args: {
49
+ mode: 'edit',
50
+ existingKey: 'LOG_LEVEL',
51
+ existingValue: 'debug'
52
+ }
53
+ };
54
+ export const SensitiveNameWarning = {
55
+ play: async ({ canvasElement })=>{
56
+ const canvas = within(canvasElement);
57
+ await userEvent.type(canvas.getByLabelText('Name'), 'MY_TOKEN');
58
+ }
59
+ };
60
+
61
+ //# sourceMappingURL=variable-form.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/variable-form.stories.tsx"],"sourcesContent":["import {Toaster} from '@shipfox/react-ui/toast';\nimport type {Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport type {ReactNode} from 'react';\nimport {useMemo} from 'react';\nimport {userEvent, within} from 'storybook/test';\nimport {VariableForm} from './variable-form.js';\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\n\nfunction Wrapper({children}: {children: ReactNode}) {\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}, mutations: {retry: false}}}),\n [],\n );\n return (\n <QueryClientProvider client={queryClient}>\n <div className=\"w-[520px] overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base\">\n {children}\n </div>\n <Toaster />\n </QueryClientProvider>\n );\n}\n\nconst meta: Meta<typeof VariableForm> = {\n title: 'Secrets/VariableForm',\n component: VariableForm,\n args: {\n workspaceId: WORKSPACE_ID,\n mode: 'create',\n onSaved: () => undefined,\n onCancel: () => undefined,\n },\n decorators: [\n (Story) => (\n <Wrapper>\n <Story />\n </Wrapper>\n ),\n ],\n};\nexport default meta;\n\ntype Story = StoryObj<typeof VariableForm>;\n\nexport const Playground: Story = {};\n\nexport const Edit: Story = {\n args: {mode: 'edit', existingKey: 'LOG_LEVEL', existingValue: 'debug'},\n};\n\nexport const SensitiveNameWarning: Story = {\n play: async ({canvasElement}) => {\n const canvas = within(canvasElement);\n await userEvent.type(canvas.getByLabelText('Name'), 'MY_TOKEN');\n },\n};\n"],"names":["Toaster","QueryClient","QueryClientProvider","useMemo","userEvent","within","VariableForm","WORKSPACE_ID","Wrapper","children","queryClient","defaultOptions","queries","retry","mutations","client","div","className","meta","title","component","args","workspaceId","mode","onSaved","undefined","onCancel","decorators","Story","Playground","Edit","existingKey","existingValue","SensitiveNameWarning","play","canvasElement","canvas","type","getByLabelText"],"mappings":";AAAA,SAAQA,OAAO,QAAO,0BAA0B;AAEhD,SAAQC,WAAW,EAAEC,mBAAmB,QAAO,wBAAwB;AAEvE,SAAQC,OAAO,QAAO,QAAQ;AAC9B,SAAQC,SAAS,EAAEC,MAAM,QAAO,iBAAiB;AACjD,SAAQC,YAAY,QAAO,qBAAqB;AAEhD,MAAMC,eAAe;AAErB,SAASC,QAAQ,EAACC,QAAQ,EAAwB;IAChD,MAAMC,cAAcP,QAClB,IAAM,IAAIF,YAAY;YAACU,gBAAgB;gBAACC,SAAS;oBAACC,OAAO;gBAAK;gBAAGC,WAAW;oBAACD,OAAO;gBAAK;YAAC;QAAC,IAC3F,EAAE;IAEJ,qBACE,MAACX;QAAoBa,QAAQL;;0BAC3B,KAACM;gBAAIC,WAAU;0BACZR;;0BAEH,KAACT;;;AAGP;AAEA,MAAMkB,OAAkC;IACtCC,OAAO;IACPC,WAAWd;IACXe,MAAM;QACJC,aAAaf;QACbgB,MAAM;QACNC,SAAS,IAAMC;QACfC,UAAU,IAAMD;IAClB;IACAE,YAAY;QACV,CAACC,sBACC,KAACpB;0BACC,cAAA,KAACoB;;KAGN;AACH;AACA,eAAeV,KAAK;AAIpB,OAAO,MAAMW,aAAoB,CAAC,EAAE;AAEpC,OAAO,MAAMC,OAAc;IACzBT,MAAM;QAACE,MAAM;QAAQQ,aAAa;QAAaC,eAAe;IAAO;AACvE,EAAE;AAEF,OAAO,MAAMC,uBAA8B;IACzCC,MAAM,OAAO,EAACC,aAAa,EAAC;QAC1B,MAAMC,SAAS/B,OAAO8B;QACtB,MAAM/B,UAAUiC,IAAI,CAACD,OAAOE,cAAc,CAAC,SAAS;IACtD;AACF,EAAE"}
@@ -0,0 +1,4 @@
1
+ export declare function WorkspaceSecretsSection({ workspaceId }: {
2
+ workspaceId: string;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=workspace-secrets-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-secrets-section.d.ts","sourceRoot":"","sources":["../../src/components/workspace-secrets-section.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,uBAAuB,CAAC,EAAC,WAAW,EAAC,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,+BAsI3E"}
@@ -0,0 +1,251 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { QueryLoadError } from '@shipfox/client-ui';
3
+ import { Button, IconButton } from '@shipfox/react-ui/button';
4
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@shipfox/react-ui/dropdown-menu';
5
+ import { EmptyState } from '@shipfox/react-ui/empty-state';
6
+ import { Modal, ModalContent, ModalHeader, ModalTitle } from '@shipfox/react-ui/modal';
7
+ import { RelativeTime, RelativeTimeProvider } from '@shipfox/react-ui/relative-time';
8
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@shipfox/react-ui/table';
9
+ import { toast } from '@shipfox/react-ui/toast';
10
+ import { Code, Header, Text } from '@shipfox/react-ui/typography';
11
+ import { useMemo, useState } from 'react';
12
+ import { useDeleteSecretMutation, useSecretsQuery } from '#hooks/api/secrets.js';
13
+ import { copyKeyName } from './copy-key.js';
14
+ import { DeleteEntryDialog } from './delete-entry-dialog.js';
15
+ import { secretsErrorToFormError } from './form-errors.js';
16
+ import { SecretForm } from './secret-form.js';
17
+ import { StoreRowsSkeleton, StoreSurface } from './store-section-shell.js';
18
+ const SECRETS_DESCRIPTION = 'Encrypted, write-only values for sensitive data like API keys, tokens, and passwords.';
19
+ const EMPTY_SECRETS_DESCRIPTION = 'Create a secret to store sensitive values like API keys, tokens, and passwords.';
20
+ export function WorkspaceSecretsSection({ workspaceId }) {
21
+ const secretsQuery = useSecretsQuery(workspaceId);
22
+ const deleteSecret = useDeleteSecretMutation();
23
+ const [formState, setFormState] = useState(null);
24
+ const [deleteKey, setDeleteKey] = useState(null);
25
+ const [deleteError, setDeleteError] = useState();
26
+ const secrets = useMemo(()=>[
27
+ ...secretsQuery.data ?? []
28
+ ].sort((a, b)=>a.key.localeCompare(b.key)), [
29
+ secretsQuery.data
30
+ ]);
31
+ function closeDelete() {
32
+ setDeleteKey(null);
33
+ setDeleteError(undefined);
34
+ }
35
+ return /*#__PURE__*/ _jsxs(RelativeTimeProvider, {
36
+ children: [
37
+ /*#__PURE__*/ _jsxs("section", {
38
+ className: "flex flex-col gap-16",
39
+ "aria-label": "Secrets",
40
+ children: [
41
+ /*#__PURE__*/ _jsxs("div", {
42
+ className: "flex items-start justify-between gap-16",
43
+ children: [
44
+ /*#__PURE__*/ _jsxs("div", {
45
+ className: "flex flex-col gap-4",
46
+ children: [
47
+ /*#__PURE__*/ _jsx(Header, {
48
+ variant: "h3",
49
+ children: "Secrets"
50
+ }),
51
+ /*#__PURE__*/ _jsx(Text, {
52
+ size: "sm",
53
+ className: "text-foreground-neutral-muted",
54
+ children: SECRETS_DESCRIPTION
55
+ })
56
+ ]
57
+ }),
58
+ /*#__PURE__*/ _jsx(Button, {
59
+ size: "sm",
60
+ onClick: ()=>setFormState({
61
+ mode: 'create'
62
+ }),
63
+ children: "Create secret"
64
+ })
65
+ ]
66
+ }),
67
+ secretsQuery.isPending ? /*#__PURE__*/ _jsx(StoreRowsSkeleton, {
68
+ label: "Loading secrets"
69
+ }) : null,
70
+ secretsQuery.isError && secretsQuery.data === undefined ? /*#__PURE__*/ _jsx(StoreSurface, {
71
+ className: "px-16",
72
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
73
+ query: secretsQuery,
74
+ subject: "secrets"
75
+ })
76
+ }) : null,
77
+ secretsQuery.data !== undefined && secrets.length === 0 ? /*#__PURE__*/ _jsx(StoreSurface, {
78
+ className: "px-16",
79
+ children: /*#__PURE__*/ _jsx(EmptyState, {
80
+ icon: "keyLine",
81
+ title: "No secrets yet",
82
+ description: EMPTY_SECRETS_DESCRIPTION,
83
+ action: /*#__PURE__*/ _jsx(Button, {
84
+ size: "sm",
85
+ onClick: ()=>setFormState({
86
+ mode: 'create'
87
+ }),
88
+ children: "Create secret"
89
+ })
90
+ })
91
+ }) : null,
92
+ secrets.length > 0 ? /*#__PURE__*/ _jsx(StoreSurface, {
93
+ children: /*#__PURE__*/ _jsxs(Table, {
94
+ children: [
95
+ /*#__PURE__*/ _jsx(TableHeader, {
96
+ children: /*#__PURE__*/ _jsxs(TableRow, {
97
+ children: [
98
+ /*#__PURE__*/ _jsx(TableHead, {
99
+ children: "Name"
100
+ }),
101
+ /*#__PURE__*/ _jsx(TableHead, {
102
+ children: "Value"
103
+ }),
104
+ /*#__PURE__*/ _jsx(TableHead, {
105
+ children: "Last edited"
106
+ }),
107
+ /*#__PURE__*/ _jsx(TableHead, {
108
+ className: "w-40 text-right",
109
+ children: /*#__PURE__*/ _jsx("span", {
110
+ className: "sr-only",
111
+ children: "Actions"
112
+ })
113
+ })
114
+ ]
115
+ })
116
+ }),
117
+ /*#__PURE__*/ _jsx(TableBody, {
118
+ children: secrets.map((secret)=>/*#__PURE__*/ _jsx(SecretRow, {
119
+ secret: secret,
120
+ onEdit: ()=>setFormState({
121
+ mode: 'edit',
122
+ key: secret.key
123
+ }),
124
+ onDelete: ()=>setDeleteKey(secret.key)
125
+ }, secret.key))
126
+ })
127
+ ]
128
+ })
129
+ }) : null
130
+ ]
131
+ }),
132
+ /*#__PURE__*/ _jsx(Modal, {
133
+ open: formState !== null,
134
+ onOpenChange: (open)=>{
135
+ if (!open) setFormState(null);
136
+ },
137
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
138
+ children: [
139
+ /*#__PURE__*/ _jsx(ModalHeader, {
140
+ children: /*#__PURE__*/ _jsx(ModalTitle, {
141
+ children: formState?.mode === 'edit' ? 'Update secret' : 'Create secret'
142
+ })
143
+ }),
144
+ formState ? /*#__PURE__*/ _jsx(SecretForm, {
145
+ workspaceId: workspaceId,
146
+ mode: formState.mode,
147
+ existingKey: formState.mode === 'edit' ? formState.key : undefined,
148
+ reservedKeys: secrets.map((secret)=>secret.key),
149
+ onSaved: ()=>{
150
+ const wasEdit = formState.mode === 'edit';
151
+ setFormState(null);
152
+ toast.success(wasEdit ? 'Secret updated' : 'Secret created');
153
+ },
154
+ onCancel: ()=>setFormState(null)
155
+ }) : null
156
+ ]
157
+ })
158
+ }),
159
+ /*#__PURE__*/ _jsx(DeleteEntryDialog, {
160
+ open: deleteKey !== null,
161
+ onOpenChange: (open)=>{
162
+ if (!open) closeDelete();
163
+ },
164
+ entryKey: deleteKey ?? '',
165
+ isLoading: deleteSecret.isPending,
166
+ errorMessage: deleteError,
167
+ onConfirm: async ()=>{
168
+ if (deleteKey === null) return;
169
+ setDeleteError(undefined);
170
+ try {
171
+ await deleteSecret.mutateAsync({
172
+ workspaceId,
173
+ key: deleteKey
174
+ });
175
+ toast.success('Secret deleted');
176
+ closeDelete();
177
+ } catch (error) {
178
+ setDeleteError(secretsErrorToFormError(error).message);
179
+ }
180
+ }
181
+ })
182
+ ]
183
+ });
184
+ }
185
+ function SecretRow({ secret, onEdit, onDelete }) {
186
+ return /*#__PURE__*/ _jsxs(TableRow, {
187
+ children: [
188
+ /*#__PURE__*/ _jsx(TableCell, {
189
+ children: /*#__PURE__*/ _jsx("button", {
190
+ type: "button",
191
+ className: "inline-flex min-w-0 cursor-pointer rounded-4 border-none bg-transparent p-0 text-left text-foreground-neutral-base outline-none transition-colors hover:text-foreground-highlight-interactive focus-visible:shadow-border-interactive-with-active",
192
+ "aria-label": `Copy secret name ${secret.key}`,
193
+ onClick: ()=>void copyKeyName(secret.key),
194
+ children: /*#__PURE__*/ _jsx(Code, {
195
+ as: "span",
196
+ variant: "paragraph",
197
+ className: "truncate",
198
+ children: secret.key
199
+ })
200
+ })
201
+ }),
202
+ /*#__PURE__*/ _jsx(TableCell, {
203
+ children: /*#__PURE__*/ _jsx("span", {
204
+ role: "img",
205
+ "aria-label": "Value hidden",
206
+ className: "font-code text-foreground-neutral-muted",
207
+ children: "••••••••"
208
+ })
209
+ }),
210
+ /*#__PURE__*/ _jsx(TableCell, {
211
+ className: "text-foreground-neutral-muted",
212
+ children: /*#__PURE__*/ _jsx(RelativeTime, {
213
+ value: secret.updated_at
214
+ })
215
+ }),
216
+ /*#__PURE__*/ _jsx(TableCell, {
217
+ className: "text-right",
218
+ children: /*#__PURE__*/ _jsxs(DropdownMenu, {
219
+ children: [
220
+ /*#__PURE__*/ _jsx(DropdownMenuTrigger, {
221
+ asChild: true,
222
+ children: /*#__PURE__*/ _jsx(IconButton, {
223
+ size: "sm",
224
+ variant: "transparent",
225
+ icon: "more2Line",
226
+ "aria-label": `Actions for ${secret.key}`
227
+ })
228
+ }),
229
+ /*#__PURE__*/ _jsxs(DropdownMenuContent, {
230
+ align: "end",
231
+ children: [
232
+ /*#__PURE__*/ _jsx(DropdownMenuItem, {
233
+ icon: "editLine",
234
+ onSelect: onEdit,
235
+ children: "Edit value"
236
+ }),
237
+ /*#__PURE__*/ _jsx(DropdownMenuItem, {
238
+ icon: "deleteBinLine",
239
+ onSelect: onDelete,
240
+ children: "Delete"
241
+ })
242
+ ]
243
+ })
244
+ ]
245
+ })
246
+ })
247
+ ]
248
+ });
249
+ }
250
+
251
+ //# sourceMappingURL=workspace-secrets-section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/workspace-secrets-section.tsx"],"sourcesContent":["import type {SecretDto} from '@shipfox/api-secrets-dto';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {Button, IconButton} from '@shipfox/react-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Modal, ModalContent, ModalHeader, ModalTitle} from '@shipfox/react-ui/modal';\nimport {RelativeTime, RelativeTimeProvider} from '@shipfox/react-ui/relative-time';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@shipfox/react-ui/table';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {useMemo, useState} from 'react';\nimport {useDeleteSecretMutation, useSecretsQuery} from '#hooks/api/secrets.js';\nimport {copyKeyName} from './copy-key.js';\nimport {DeleteEntryDialog} from './delete-entry-dialog.js';\nimport {secretsErrorToFormError} from './form-errors.js';\nimport {SecretForm} from './secret-form.js';\nimport {StoreRowsSkeleton, StoreSurface} from './store-section-shell.js';\n\nconst SECRETS_DESCRIPTION =\n 'Encrypted, write-only values for sensitive data like API keys, tokens, and passwords.';\nconst EMPTY_SECRETS_DESCRIPTION =\n 'Create a secret to store sensitive values like API keys, tokens, and passwords.';\n\ntype FormState = {mode: 'create'} | {mode: 'edit'; key: string} | null;\n\nexport function WorkspaceSecretsSection({workspaceId}: {workspaceId: string}) {\n const secretsQuery = useSecretsQuery(workspaceId);\n const deleteSecret = useDeleteSecretMutation();\n const [formState, setFormState] = useState<FormState>(null);\n const [deleteKey, setDeleteKey] = useState<string | null>(null);\n const [deleteError, setDeleteError] = useState<string | undefined>();\n\n const secrets = useMemo(\n () => [...(secretsQuery.data ?? [])].sort((a, b) => a.key.localeCompare(b.key)),\n [secretsQuery.data],\n );\n\n function closeDelete() {\n setDeleteKey(null);\n setDeleteError(undefined);\n }\n\n return (\n <RelativeTimeProvider>\n <section className=\"flex flex-col gap-16\" aria-label=\"Secrets\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Secrets</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {SECRETS_DESCRIPTION}\n </Text>\n </div>\n <Button size=\"sm\" onClick={() => setFormState({mode: 'create'})}>\n Create secret\n </Button>\n </div>\n\n {secretsQuery.isPending ? <StoreRowsSkeleton label=\"Loading secrets\" /> : null}\n\n {secretsQuery.isError && secretsQuery.data === undefined ? (\n <StoreSurface className=\"px-16\">\n <QueryLoadError query={secretsQuery} subject=\"secrets\" />\n </StoreSurface>\n ) : null}\n\n {secretsQuery.data !== undefined && secrets.length === 0 ? (\n <StoreSurface className=\"px-16\">\n <EmptyState\n icon=\"keyLine\"\n title=\"No secrets yet\"\n description={EMPTY_SECRETS_DESCRIPTION}\n action={\n <Button size=\"sm\" onClick={() => setFormState({mode: 'create'})}>\n Create secret\n </Button>\n }\n />\n </StoreSurface>\n ) : null}\n\n {secrets.length > 0 ? (\n <StoreSurface>\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Value</TableHead>\n <TableHead>Last edited</TableHead>\n <TableHead className=\"w-40 text-right\">\n <span className=\"sr-only\">Actions</span>\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {secrets.map((secret) => (\n <SecretRow\n key={secret.key}\n secret={secret}\n onEdit={() => setFormState({mode: 'edit', key: secret.key})}\n onDelete={() => setDeleteKey(secret.key)}\n />\n ))}\n </TableBody>\n </Table>\n </StoreSurface>\n ) : null}\n </section>\n\n <Modal\n open={formState !== null}\n onOpenChange={(open) => {\n if (!open) setFormState(null);\n }}\n >\n <ModalContent>\n <ModalHeader>\n <ModalTitle>\n {formState?.mode === 'edit' ? 'Update secret' : 'Create secret'}\n </ModalTitle>\n </ModalHeader>\n {formState ? (\n <SecretForm\n workspaceId={workspaceId}\n mode={formState.mode}\n existingKey={formState.mode === 'edit' ? formState.key : undefined}\n reservedKeys={secrets.map((secret) => secret.key)}\n onSaved={() => {\n const wasEdit = formState.mode === 'edit';\n setFormState(null);\n toast.success(wasEdit ? 'Secret updated' : 'Secret created');\n }}\n onCancel={() => setFormState(null)}\n />\n ) : null}\n </ModalContent>\n </Modal>\n\n <DeleteEntryDialog\n open={deleteKey !== null}\n onOpenChange={(open) => {\n if (!open) closeDelete();\n }}\n entryKey={deleteKey ?? ''}\n isLoading={deleteSecret.isPending}\n errorMessage={deleteError}\n onConfirm={async () => {\n if (deleteKey === null) return;\n setDeleteError(undefined);\n try {\n await deleteSecret.mutateAsync({workspaceId, key: deleteKey});\n toast.success('Secret deleted');\n closeDelete();\n } catch (error) {\n setDeleteError(secretsErrorToFormError(error).message);\n }\n }}\n />\n </RelativeTimeProvider>\n );\n}\n\nfunction SecretRow({\n secret,\n onEdit,\n onDelete,\n}: {\n secret: SecretDto;\n onEdit: () => void;\n onDelete: () => void;\n}) {\n return (\n <TableRow>\n <TableCell>\n <button\n type=\"button\"\n className=\"inline-flex min-w-0 cursor-pointer rounded-4 border-none bg-transparent p-0 text-left text-foreground-neutral-base outline-none transition-colors hover:text-foreground-highlight-interactive focus-visible:shadow-border-interactive-with-active\"\n aria-label={`Copy secret name ${secret.key}`}\n onClick={() => void copyKeyName(secret.key)}\n >\n <Code as=\"span\" variant=\"paragraph\" className=\"truncate\">\n {secret.key}\n </Code>\n </button>\n </TableCell>\n <TableCell>\n <span\n role=\"img\"\n aria-label=\"Value hidden\"\n className=\"font-code text-foreground-neutral-muted\"\n >\n ••••••••\n </span>\n </TableCell>\n <TableCell className=\"text-foreground-neutral-muted\">\n <RelativeTime value={secret.updated_at} />\n </TableCell>\n <TableCell className=\"text-right\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton\n size=\"sm\"\n variant=\"transparent\"\n icon=\"more2Line\"\n aria-label={`Actions for ${secret.key}`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem icon=\"editLine\" onSelect={onEdit}>\n Edit value\n </DropdownMenuItem>\n <DropdownMenuItem icon=\"deleteBinLine\" onSelect={onDelete}>\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </TableCell>\n </TableRow>\n );\n}\n"],"names":["QueryLoadError","Button","IconButton","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","EmptyState","Modal","ModalContent","ModalHeader","ModalTitle","RelativeTime","RelativeTimeProvider","Table","TableBody","TableCell","TableHead","TableHeader","TableRow","toast","Code","Header","Text","useMemo","useState","useDeleteSecretMutation","useSecretsQuery","copyKeyName","DeleteEntryDialog","secretsErrorToFormError","SecretForm","StoreRowsSkeleton","StoreSurface","SECRETS_DESCRIPTION","EMPTY_SECRETS_DESCRIPTION","WorkspaceSecretsSection","workspaceId","secretsQuery","deleteSecret","formState","setFormState","deleteKey","setDeleteKey","deleteError","setDeleteError","secrets","data","sort","a","b","key","localeCompare","closeDelete","undefined","section","className","aria-label","div","variant","size","onClick","mode","isPending","label","isError","query","subject","length","icon","title","description","action","span","map","secret","SecretRow","onEdit","onDelete","open","onOpenChange","existingKey","reservedKeys","onSaved","wasEdit","success","onCancel","entryKey","isLoading","errorMessage","onConfirm","mutateAsync","error","message","button","type","as","role","value","updated_at","asChild","align","onSelect"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ type Scenario = 'loaded' | 'empty' | 'loading' | 'error';
3
+ declare function SectionStory({ scenario }: {
4
+ scenario: Scenario;
5
+ }): import("react").JSX.Element;
6
+ declare const meta: Meta<typeof SectionStory>;
7
+ export default meta;
8
+ type Story = StoryObj<typeof SectionStory>;
9
+ export declare const Loaded: Story;
10
+ export declare const Empty: Story;
11
+ export declare const Loading: Story;
12
+ export declare const LoadError: Story;
13
+ //# sourceMappingURL=workspace-secrets-section.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-secrets-section.stories.d.ts","sourceRoot":"","sources":["../../src/components/workspace-secrets-section.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAY,IAAI,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAgDhE,KAAK,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAyBzD,iBAAS,YAAY,CAAC,EAAC,QAAQ,EAAC,EAAE;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAC,+BAcrD;AAED,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,YAAY,CAInC,CAAC;AACF,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3C,eAAO,MAAM,MAAM,EAAE,KAQpB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAKnB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,KAKvB,CAAC"}
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { configureApiClient } from '@shipfox/client-api';
3
+ import { Toaster } from '@shipfox/react-ui/toast';
4
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
+ import { useEffect, useMemo } from 'react';
6
+ import { within } from 'storybook/test';
7
+ import { WorkspaceSecretsSection } from './workspace-secrets-section.js';
8
+ // Freeze the clock only while a story is mounted so RelativeTime renders a stable
9
+ // string in argos snapshots, without leaking the override to other stories.
10
+ const FROZEN_NOW = Date.parse('2026-07-02T00:00:00.000Z');
11
+ const realDateNow = Date.now;
12
+ const withFrozenClock = (Story)=>{
13
+ Date.now = ()=>FROZEN_NOW;
14
+ useEffect(()=>()=>{
15
+ Date.now = realDateNow;
16
+ }, []);
17
+ return /*#__PURE__*/ _jsx(Story, {});
18
+ };
19
+ const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
20
+ const EDITOR_ID = '22222222-2222-4222-8222-222222222222';
21
+ const SECRETS = [
22
+ {
23
+ key: 'API_TOKEN',
24
+ project_id: null,
25
+ created_at: '2026-05-01T10:00:00.000Z',
26
+ updated_at: '2026-06-30T12:00:00.000Z',
27
+ last_edited_by: EDITOR_ID
28
+ },
29
+ {
30
+ key: 'DATABASE_URL',
31
+ project_id: null,
32
+ created_at: '2026-04-01T10:00:00.000Z',
33
+ updated_at: '2026-06-25T12:00:00.000Z',
34
+ last_edited_by: EDITOR_ID
35
+ },
36
+ {
37
+ key: 'STRIPE_SECRET_KEY',
38
+ project_id: null,
39
+ created_at: '2026-03-01T10:00:00.000Z',
40
+ updated_at: '2026-06-20T12:00:00.000Z',
41
+ last_edited_by: null
42
+ }
43
+ ];
44
+ const RETRY_BUTTON = /retry/i;
45
+ function fetchForScenario(scenario) {
46
+ return ()=>{
47
+ if (scenario === 'loading') return new Promise(()=>undefined);
48
+ if (scenario === 'error') {
49
+ return Promise.resolve(new Response(JSON.stringify({
50
+ code: 'server-error',
51
+ message: 'Something failed'
52
+ }), {
53
+ status: 500,
54
+ headers: {
55
+ 'content-type': 'application/json'
56
+ }
57
+ }));
58
+ }
59
+ const secrets = scenario === 'empty' ? [] : SECRETS;
60
+ return Promise.resolve(new Response(JSON.stringify({
61
+ secrets,
62
+ next_cursor: null
63
+ }), {
64
+ status: 200,
65
+ headers: {
66
+ 'content-type': 'application/json'
67
+ }
68
+ }));
69
+ };
70
+ }
71
+ function SectionStory({ scenario }) {
72
+ configureApiClient({
73
+ baseUrl: 'https://api.example.test',
74
+ fetchImpl: fetchForScenario(scenario)
75
+ });
76
+ const queryClient = useMemo(()=>new QueryClient({
77
+ defaultOptions: {
78
+ queries: {
79
+ retry: false
80
+ }
81
+ }
82
+ }), []);
83
+ return /*#__PURE__*/ _jsxs(QueryClientProvider, {
84
+ client: queryClient,
85
+ children: [
86
+ /*#__PURE__*/ _jsx("div", {
87
+ className: "w-[720px] p-24",
88
+ children: /*#__PURE__*/ _jsx(WorkspaceSecretsSection, {
89
+ workspaceId: WORKSPACE_ID
90
+ })
91
+ }),
92
+ /*#__PURE__*/ _jsx(Toaster, {})
93
+ ]
94
+ });
95
+ }
96
+ const meta = {
97
+ title: 'Secrets/WorkspaceSecretsSection',
98
+ component: SectionStory,
99
+ decorators: [
100
+ withFrozenClock
101
+ ]
102
+ };
103
+ export default meta;
104
+ export const Loaded = {
105
+ args: {
106
+ scenario: 'loaded'
107
+ },
108
+ play: async ({ canvasElement })=>{
109
+ const canvas = within(canvasElement);
110
+ await canvas.findByText('API_TOKEN');
111
+ await canvas.findByRole('button', {
112
+ name: 'Copy secret name API_TOKEN'
113
+ });
114
+ }
115
+ };
116
+ export const Empty = {
117
+ args: {
118
+ scenario: 'empty'
119
+ },
120
+ play: async ({ canvasElement })=>{
121
+ await within(canvasElement).findByText('No secrets yet');
122
+ }
123
+ };
124
+ export const Loading = {
125
+ args: {
126
+ scenario: 'loading'
127
+ }
128
+ };
129
+ export const LoadError = {
130
+ args: {
131
+ scenario: 'error'
132
+ },
133
+ play: async ({ canvasElement })=>{
134
+ await within(canvasElement).findByRole('button', {
135
+ name: RETRY_BUTTON
136
+ });
137
+ }
138
+ };
139
+
140
+ //# sourceMappingURL=workspace-secrets-section.stories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/workspace-secrets-section.stories.tsx"],"sourcesContent":["import type {SecretDto} from '@shipfox/api-secrets-dto';\nimport {configureApiClient} from '@shipfox/client-api';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport type {Decorator, Meta, StoryObj} from '@storybook/react';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {useEffect, useMemo} from 'react';\nimport {within} from 'storybook/test';\nimport {WorkspaceSecretsSection} from './workspace-secrets-section.js';\n\n// Freeze the clock only while a story is mounted so RelativeTime renders a stable\n// string in argos snapshots, without leaking the override to other stories.\nconst FROZEN_NOW = Date.parse('2026-07-02T00:00:00.000Z');\nconst realDateNow = Date.now;\nconst withFrozenClock: Decorator = (Story) => {\n Date.now = () => FROZEN_NOW;\n useEffect(\n () => () => {\n Date.now = realDateNow;\n },\n [],\n );\n return <Story />;\n};\n\nconst WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';\nconst EDITOR_ID = '22222222-2222-4222-8222-222222222222';\n\nconst SECRETS: SecretDto[] = [\n {\n key: 'API_TOKEN',\n project_id: null,\n created_at: '2026-05-01T10:00:00.000Z',\n updated_at: '2026-06-30T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'DATABASE_URL',\n project_id: null,\n created_at: '2026-04-01T10:00:00.000Z',\n updated_at: '2026-06-25T12:00:00.000Z',\n last_edited_by: EDITOR_ID,\n },\n {\n key: 'STRIPE_SECRET_KEY',\n project_id: null,\n created_at: '2026-03-01T10:00:00.000Z',\n updated_at: '2026-06-20T12:00:00.000Z',\n last_edited_by: null,\n },\n];\n\ntype Scenario = 'loaded' | 'empty' | 'loading' | 'error';\n\nconst RETRY_BUTTON = /retry/i;\n\nfunction fetchForScenario(scenario: Scenario): typeof fetch {\n return (() => {\n if (scenario === 'loading') return new Promise<Response>(() => undefined);\n if (scenario === 'error') {\n return Promise.resolve(\n new Response(JSON.stringify({code: 'server-error', message: 'Something failed'}), {\n status: 500,\n headers: {'content-type': 'application/json'},\n }),\n );\n }\n const secrets = scenario === 'empty' ? [] : SECRETS;\n return Promise.resolve(\n new Response(JSON.stringify({secrets, next_cursor: null}), {\n status: 200,\n headers: {'content-type': 'application/json'},\n }),\n );\n }) as unknown as typeof fetch;\n}\n\nfunction SectionStory({scenario}: {scenario: Scenario}) {\n configureApiClient({baseUrl: 'https://api.example.test', fetchImpl: fetchForScenario(scenario)});\n const queryClient = useMemo(\n () => new QueryClient({defaultOptions: {queries: {retry: false}}}),\n [],\n );\n return (\n <QueryClientProvider client={queryClient}>\n <div className=\"w-[720px] p-24\">\n <WorkspaceSecretsSection workspaceId={WORKSPACE_ID} />\n </div>\n <Toaster />\n </QueryClientProvider>\n );\n}\n\nconst meta: Meta<typeof SectionStory> = {\n title: 'Secrets/WorkspaceSecretsSection',\n component: SectionStory,\n decorators: [withFrozenClock],\n};\nexport default meta;\n\ntype Story = StoryObj<typeof SectionStory>;\n\nexport const Loaded: Story = {\n args: {scenario: 'loaded'},\n play: async ({canvasElement}) => {\n const canvas = within(canvasElement);\n\n await canvas.findByText('API_TOKEN');\n await canvas.findByRole('button', {name: 'Copy secret name API_TOKEN'});\n },\n};\n\nexport const Empty: Story = {\n args: {scenario: 'empty'},\n play: async ({canvasElement}) => {\n await within(canvasElement).findByText('No secrets yet');\n },\n};\n\nexport const Loading: Story = {\n args: {scenario: 'loading'},\n};\n\nexport const LoadError: Story = {\n args: {scenario: 'error'},\n play: async ({canvasElement}) => {\n await within(canvasElement).findByRole('button', {name: RETRY_BUTTON});\n },\n};\n"],"names":["configureApiClient","Toaster","QueryClient","QueryClientProvider","useEffect","useMemo","within","WorkspaceSecretsSection","FROZEN_NOW","Date","parse","realDateNow","now","withFrozenClock","Story","WORKSPACE_ID","EDITOR_ID","SECRETS","key","project_id","created_at","updated_at","last_edited_by","RETRY_BUTTON","fetchForScenario","scenario","Promise","undefined","resolve","Response","JSON","stringify","code","message","status","headers","secrets","next_cursor","SectionStory","baseUrl","fetchImpl","queryClient","defaultOptions","queries","retry","client","div","className","workspaceId","meta","title","component","decorators","Loaded","args","play","canvasElement","canvas","findByText","findByRole","name","Empty","Loading","LoadError"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function WorkspaceVariablesSection({ workspaceId }: {
2
+ workspaceId: string;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=workspace-variables-section.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-variables-section.d.ts","sourceRoot":"","sources":["../../src/components/workspace-variables-section.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,yBAAyB,CAAC,EAAC,WAAW,EAAC,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,+BA0I7E"}