@cosmicdrift/kumiko-renderer 0.327.0 → 0.328.1
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.
- package/dist/app/action-form-shim.d.ts +14 -0
- package/{src/app/action-form-shim.ts → dist/app/action-form-shim.js} +26 -44
- package/dist/app/app-features-context.d.ts +8 -0
- package/dist/app/app-features-context.js +16 -0
- package/dist/app/column-renderers.d.ts +30 -0
- package/dist/app/column-renderers.js +34 -0
- package/dist/app/config-edit-shim.d.ts +11 -0
- package/{src/app/config-edit-shim.ts → dist/app/config-edit-shim.js} +12 -25
- package/dist/app/content-editors.d.ts +38 -0
- package/dist/app/content-editors.js +26 -0
- package/dist/app/content-preview.d.ts +11 -0
- package/dist/app/content-preview.js +30 -0
- package/dist/app/custom-screens.d.ts +11 -0
- package/dist/app/custom-screens.js +20 -0
- package/dist/app/dashboard-body.d.ts +16 -0
- package/dist/app/dashboard-body.js +11 -0
- package/dist/app/extension-form-submit.d.ts +32 -0
- package/dist/app/extension-form-submit.js +73 -0
- package/dist/app/extension-sections.d.ts +72 -0
- package/dist/app/extension-sections.js +34 -0
- package/dist/app/feature-schema.d.ts +4 -0
- package/{src/app/feature-schema.ts → dist/app/feature-schema.js} +12 -22
- package/dist/app/form-schema.d.ts +4 -0
- package/dist/app/form-schema.js +85 -0
- package/dist/app/kumiko-screen.d.ts +43 -0
- package/dist/app/kumiko-screen.js +2206 -0
- package/dist/app/layout-fields.d.ts +3 -0
- package/dist/app/layout-fields.js +20 -0
- package/dist/app/list-facets.d.ts +48 -0
- package/dist/app/list-facets.js +185 -0
- package/dist/app/nav.d.ts +61 -0
- package/dist/app/nav.js +139 -0
- package/dist/app/projection-detail-shim.d.ts +8 -0
- package/dist/app/projection-detail-shim.js +71 -0
- package/dist/app/projection-list-shim.d.ts +13 -0
- package/{src/app/projection-list-shim.ts → dist/app/projection-list-shim.js} +26 -38
- package/dist/app/qn.d.ts +7 -0
- package/{src/app/qn.ts → dist/app/qn.js} +9 -11
- package/dist/app/qualify-screen-id.d.ts +2 -0
- package/{src/app/qualify-screen-id.ts → dist/app/qualify-screen-id.js} +5 -6
- package/dist/app/reference-facet-bridge.d.ts +10 -0
- package/dist/app/reference-facet-bridge.js +22 -0
- package/dist/app/return-to.d.ts +31 -0
- package/dist/app/return-to.js +188 -0
- package/dist/app/row-actions.d.ts +77 -0
- package/dist/app/row-actions.js +416 -0
- package/dist/app/screen-access.d.ts +8 -0
- package/dist/app/screen-access.js +48 -0
- package/dist/app/secret-mint-body.d.ts +10 -0
- package/dist/app/secret-mint-body.js +140 -0
- package/dist/app/secrets-edit-body.d.ts +8 -0
- package/dist/app/secrets-edit-body.js +76 -0
- package/dist/app/use-embedded-screen.d.ts +6 -0
- package/dist/app/use-embedded-screen.js +19 -0
- package/dist/app/variable-chips.d.ts +7 -0
- package/dist/app/variable-chips.js +16 -0
- package/dist/app/write-failed-error.d.ts +6 -0
- package/dist/app/write-failed-error.js +21 -0
- package/dist/components/embedded-list-field.d.ts +10 -0
- package/dist/components/embedded-list-field.js +259 -0
- package/dist/components/grid-cell-for-field.d.ts +25 -0
- package/dist/components/grid-cell-for-field.js +31 -0
- package/dist/components/multi-select-checkboxes.d.ts +14 -0
- package/dist/components/multi-select-checkboxes.js +36 -0
- package/dist/components/reference-create-dialog.d.ts +13 -0
- package/dist/components/reference-create-dialog.js +46 -0
- package/dist/components/related-list-section.d.ts +25 -0
- package/dist/components/related-list-section.js +273 -0
- package/dist/components/render-edit-action-button.d.ts +22 -0
- package/dist/components/render-edit-action-button.js +43 -0
- package/dist/components/render-edit-logic.d.ts +6 -0
- package/dist/components/render-edit-logic.js +63 -0
- package/dist/components/render-edit-types.d.ts +209 -0
- package/dist/components/render-edit-types.js +1 -0
- package/dist/components/render-edit.d.ts +6 -0
- package/dist/components/render-edit.js +1169 -0
- package/dist/components/render-field.d.ts +38 -0
- package/dist/components/render-field.js +544 -0
- package/dist/components/render-list.d.ts +117 -0
- package/dist/components/render-list.js +232 -0
- package/dist/components/write-form-section.d.ts +17 -0
- package/dist/components/write-form-section.js +104 -0
- package/dist/context/dispatcher-context.d.ts +10 -0
- package/{src/context/dispatcher-context.tsx → dist/context/dispatcher-context.js} +16 -29
- package/dist/context/draft-storage-context.d.ts +12 -0
- package/dist/context/draft-storage-context.js +18 -0
- package/dist/context/user-roles-context.d.ts +7 -0
- package/{src/context/user-roles-context.tsx → dist/context/user-roles-context.js} +7 -15
- package/dist/format-when.d.ts +1 -0
- package/{src/format-when.ts → dist/format-when.js} +7 -7
- package/dist/hooks/reference-limits.d.ts +3 -0
- package/{src/hooks/reference-limits.ts → dist/hooks/reference-limits.js} +0 -1
- package/dist/hooks/use-ai-text.d.ts +52 -0
- package/dist/hooks/use-ai-text.js +90 -0
- package/dist/hooks/use-disclosure.d.ts +7 -0
- package/dist/hooks/use-disclosure.js +8 -0
- package/dist/hooks/use-form.d.ts +7 -0
- package/dist/hooks/use-form.js +36 -0
- package/dist/hooks/use-list-url-state.d.ts +43 -0
- package/dist/hooks/use-list-url-state.js +131 -0
- package/dist/hooks/use-mutation.d.ts +9 -0
- package/dist/hooks/use-mutation.js +37 -0
- package/dist/hooks/use-query.d.ts +14 -0
- package/dist/hooks/use-query.js +118 -0
- package/dist/hooks/use-reference-lookup.d.ts +14 -0
- package/dist/hooks/use-reference-lookup.js +63 -0
- package/dist/hooks/use-store.d.ts +3 -0
- package/dist/hooks/use-store.js +38 -0
- package/dist/hooks/use-stream-handler.d.ts +14 -0
- package/dist/hooks/use-stream-handler.js +93 -0
- package/dist/i18n-defaults.d.ts +2 -0
- package/dist/i18n-defaults.js +201 -0
- package/dist/i18n.d.ts +64 -0
- package/dist/i18n.js +166 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +55 -0
- package/dist/inside-drawer.d.ts +2 -0
- package/dist/inside-drawer.js +9 -0
- package/dist/page-header-slot.d.ts +2 -0
- package/dist/page-header-slot.js +9 -0
- package/dist/primitives.d.ts +1351 -0
- package/dist/primitives.js +73 -0
- package/dist/screen-fills-height.d.ts +3 -0
- package/dist/screen-fills-height.js +3 -0
- package/dist/share-links.d.ts +4 -0
- package/dist/share-links.js +11 -0
- package/dist/sort-by-accessor.d.ts +5 -0
- package/dist/sort-by-accessor.js +16 -0
- package/dist/sse/live-events.d.ts +23 -0
- package/dist/sse/live-events.js +28 -0
- package/dist/tokens.d.ts +83 -0
- package/dist/tokens.js +80 -0
- package/package.json +16 -9
- package/src/changes.json +52 -0
- package/src/__tests__/error-i18n-defaults.test.ts +0 -82
- package/src/__tests__/format-when.test.ts +0 -12
- package/src/__tests__/i18n.test.tsx +0 -283
- package/src/__tests__/merge-search-params-into-initial.test.ts +0 -525
- package/src/__tests__/qn.test.ts +0 -83
- package/src/__tests__/share-links.test.ts +0 -26
- package/src/__tests__/sort-by-accessor.test.ts +0 -48
- package/src/__tests__/stringify-nav-params.test.ts +0 -33
- package/src/__tests__/use-list-url-state.test.tsx +0 -250
- package/src/app/__tests__/action-form-field-labels.test.tsx +0 -92
- package/src/app/__tests__/action-form-shim.test.ts +0 -137
- package/src/app/__tests__/build-list-query-payload.test.ts +0 -107
- package/src/app/__tests__/config-edit-shim.test.ts +0 -57
- package/src/app/__tests__/content-editors.test.tsx +0 -128
- package/src/app/__tests__/content-preview.test.tsx +0 -184
- package/src/app/__tests__/entity-edit-create-search-params.test.tsx +0 -236
- package/src/app/__tests__/entity-edit-jsonb-embedded-submit.test.tsx +0 -239
- package/src/app/__tests__/entity-list-create-singleton.test.tsx +0 -128
- package/src/app/__tests__/entity-list-row-action-entity-target.test.tsx +0 -424
- package/src/app/__tests__/entity-list-row-action-icon.test.tsx +0 -248
- package/src/app/__tests__/entity-list-row-action-kumiko-actions-view.test.tsx +0 -162
- package/src/app/__tests__/entity-list-row-action-refetch.test.tsx +0 -401
- package/src/app/__tests__/entity-list-screen-padding.test.tsx +0 -111
- package/src/app/__tests__/feature-schema.test.ts +0 -40
- package/src/app/__tests__/form-schema.test.ts +0 -247
- package/src/app/__tests__/list-daterange-facet.test.ts +0 -165
- package/src/app/__tests__/list-filter-facets.test.tsx +0 -521
- package/src/app/__tests__/nav.test.ts +0 -198
- package/src/app/__tests__/projection-detail-actions.test.tsx +0 -719
- package/src/app/__tests__/projection-detail-reference-field.test.tsx +0 -165
- package/src/app/__tests__/projection-detail-shim.test.ts +0 -188
- package/src/app/__tests__/projection-detail-singleton.test.tsx +0 -191
- package/src/app/__tests__/projection-list-bad-shape.test.tsx +0 -152
- package/src/app/__tests__/projection-list-reference-column.test.tsx +0 -219
- package/src/app/__tests__/projection-list-search-sort.test.tsx +0 -212
- package/src/app/__tests__/qualified-names.test.ts +0 -37
- package/src/app/__tests__/return-to.test.ts +0 -396
- package/src/app/__tests__/row-actions-navigate-tab.test.ts +0 -223
- package/src/app/__tests__/row-default-edit-action.test.tsx +0 -324
- package/src/app/__tests__/screen-access-allows.test.ts +0 -34
- package/src/app/__tests__/secret-mint-body.test.tsx +0 -404
- package/src/app/__tests__/variable-chips.test.tsx +0 -70
- package/src/app/app-features-context.tsx +0 -25
- package/src/app/column-renderers.tsx +0 -68
- package/src/app/content-editors.tsx +0 -85
- package/src/app/content-preview.tsx +0 -67
- package/src/app/custom-screens.tsx +0 -29
- package/src/app/dashboard-body.tsx +0 -33
- package/src/app/extension-form-submit.tsx +0 -123
- package/src/app/extension-sections.tsx +0 -111
- package/src/app/form-schema.ts +0 -91
- package/src/app/kumiko-screen.tsx +0 -3614
- package/src/app/layout-fields.ts +0 -31
- package/src/app/list-facets.ts +0 -259
- package/src/app/nav.tsx +0 -258
- package/src/app/projection-detail-shim.ts +0 -85
- package/src/app/reference-facet-bridge.tsx +0 -58
- package/src/app/return-to.tsx +0 -234
- package/src/app/row-actions.ts +0 -639
- package/src/app/screen-access.ts +0 -63
- package/src/app/secret-mint-body.tsx +0 -259
- package/src/app/secrets-edit-body.tsx +0 -194
- package/src/app/use-embedded-screen.ts +0 -27
- package/src/app/variable-chips.tsx +0 -39
- package/src/app/write-failed-error.ts +0 -27
- package/src/components/__tests__/embedded-list-field.test.tsx +0 -531
- package/src/components/__tests__/multi-select-checkboxes.test.tsx +0 -162
- package/src/components/__tests__/related-list-section-empty-state.test.tsx +0 -143
- package/src/components/__tests__/related-list-section.test.tsx +0 -1669
- package/src/components/__tests__/render-edit-action-button.test.tsx +0 -205
- package/src/components/__tests__/render-edit-hidden-section.test.tsx +0 -128
- package/src/components/__tests__/render-edit-logic.test.ts +0 -314
- package/src/components/__tests__/render-edit-screen-description.test.tsx +0 -152
- package/src/components/__tests__/render-edit-submit-actions.test.tsx +0 -460
- package/src/components/__tests__/render-field-app-locale.test.tsx +0 -142
- package/src/components/__tests__/render-field-enum-option.test.tsx +0 -99
- package/src/components/__tests__/render-field-icon-derivation.test.tsx +0 -119
- package/src/components/__tests__/render-field-json-format.test.tsx +0 -164
- package/src/components/__tests__/render-field-money-embedded-list-consistency.test.tsx +0 -212
- package/src/components/__tests__/render-field-money-roundtrip.test.tsx +0 -217
- package/src/components/__tests__/render-field-number-grouping.test.tsx +0 -76
- package/src/components/__tests__/render-field-number-unit.test.tsx +0 -227
- package/src/components/__tests__/render-field-password-format.test.tsx +0 -101
- package/src/components/__tests__/render-field-select-display.test.tsx +0 -134
- package/src/components/__tests__/render-field-unit-format.test.tsx +0 -107
- package/src/components/__tests__/render-field-unsupported-types.test.tsx +0 -195
- package/src/components/__tests__/render-list-toolbar-icon-only.test.tsx +0 -146
- package/src/components/__tests__/write-form-section.test.tsx +0 -375
- package/src/components/embedded-list-field.tsx +0 -354
- package/src/components/grid-cell-for-field.tsx +0 -60
- package/src/components/multi-select-checkboxes.tsx +0 -93
- package/src/components/reference-create-dialog.tsx +0 -91
- package/src/components/related-list-section.tsx +0 -467
- package/src/components/render-edit-action-button.tsx +0 -106
- package/src/components/render-edit-logic.ts +0 -85
- package/src/components/render-edit-types.ts +0 -216
- package/src/components/render-edit.tsx +0 -1700
- package/src/components/render-field.tsx +0 -942
- package/src/components/render-list.tsx +0 -561
- package/src/components/write-form-section.tsx +0 -199
- package/src/context/draft-storage-context.tsx +0 -39
- package/src/hooks/__tests__/use-ai-text.test.tsx +0 -177
- package/src/hooks/__tests__/use-disclosure.test.tsx +0 -25
- package/src/hooks/__tests__/use-form.test.tsx +0 -65
- package/src/hooks/__tests__/use-mutation.test.tsx +0 -77
- package/src/hooks/__tests__/use-stream-handler.test.tsx +0 -107
- package/src/hooks/use-ai-text.ts +0 -172
- package/src/hooks/use-disclosure.ts +0 -20
- package/src/hooks/use-form.ts +0 -78
- package/src/hooks/use-list-url-state.ts +0 -171
- package/src/hooks/use-mutation.ts +0 -61
- package/src/hooks/use-query.ts +0 -132
- package/src/hooks/use-reference-lookup.ts +0 -76
- package/src/hooks/use-store.ts +0 -48
- package/src/hooks/use-stream-handler.ts +0 -134
- package/src/i18n-defaults.ts +0 -220
- package/src/i18n.tsx +0 -286
- package/src/index.ts +0 -272
- package/src/primitives.tsx +0 -1446
- package/src/share-links.ts +0 -12
- package/src/sort-by-accessor.ts +0 -20
- package/src/sse/live-events.tsx +0 -56
- package/src/tokens.tsx +0 -170
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import type { SecretMintScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
-
import type { FormValues, SubmitResult, Translate } from "@cosmicdrift/kumiko-headless";
|
|
3
|
-
import { type ReactNode, useCallback, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { RenderEdit } from "../components/render-edit";
|
|
5
|
-
import { useTranslation } from "../i18n";
|
|
6
|
-
import { usePrimitives } from "../primitives";
|
|
7
|
-
import {
|
|
8
|
-
synthesizeActionFormEntity,
|
|
9
|
-
synthesizeActionFormScreen,
|
|
10
|
-
synthesizeSecretMintConfirmScreen,
|
|
11
|
-
} from "./action-form-shim";
|
|
12
|
-
import type { FeatureSchema } from "./feature-schema";
|
|
13
|
-
import {
|
|
14
|
-
buildInitialValues,
|
|
15
|
-
literalCurrencyOverrides,
|
|
16
|
-
mergeSearchParamsIntoInitial,
|
|
17
|
-
} from "./kumiko-screen";
|
|
18
|
-
import { layoutFieldNames } from "./layout-fields";
|
|
19
|
-
import { useInitialValuesHandoff, useNav } from "./nav";
|
|
20
|
-
import { lastSegment } from "./qn";
|
|
21
|
-
import { navigateToReturnOr, useReturnTarget } from "./return-to";
|
|
22
|
-
|
|
23
|
-
export type SecretMintBodyProps = {
|
|
24
|
-
readonly schema: FeatureSchema;
|
|
25
|
-
readonly screen: SecretMintScreenDefinition;
|
|
26
|
-
readonly translate?: Translate;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function extractRevealValue(data: unknown, field: string): unknown {
|
|
30
|
-
if (typeof data !== "object" || data === null) return undefined;
|
|
31
|
-
if (!Object.hasOwn(data, field)) return undefined;
|
|
32
|
-
return (data as Record<string, unknown>)[field];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function extractCarriedValues(
|
|
36
|
-
data: unknown,
|
|
37
|
-
carry: readonly string[],
|
|
38
|
-
): Readonly<Record<string, unknown>> {
|
|
39
|
-
const carried: Record<string, unknown> = {};
|
|
40
|
-
for (const field of carry) {
|
|
41
|
-
const value = extractRevealValue(data, field);
|
|
42
|
-
if (value === undefined) continue;
|
|
43
|
-
carried[field] = value;
|
|
44
|
-
}
|
|
45
|
-
return carried;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function isBlank(value: unknown): boolean {
|
|
49
|
-
if (value === undefined || value === null || value === "") return true;
|
|
50
|
-
return Array.isArray(value) && value.length === 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Mint form → one-time reveal → optional confirm → done. The revealed values
|
|
54
|
-
// live ONLY in this component's `revealed` state — never in the URL, a query
|
|
55
|
-
// cache, or nav — the write-handler's success payload is the only place the
|
|
56
|
-
// secret ever exists (fw#2548). onSubmit copies exclusively the fields
|
|
57
|
-
// declared in `screen.reveal.fields` out of that payload (a whitelist, never
|
|
58
|
-
// the payload as a whole) so an unrelated field (e.g. an internal "id") can
|
|
59
|
-
// never leak into the reveal. `screen.confirm` (fw#2838, e.g. TOTP enroll:
|
|
60
|
-
// scan the code, then enter one) works the same way for its own carried
|
|
61
|
-
// mint-payload fields — those live only in `carriedRef`, never in React state
|
|
62
|
-
// that gets rendered, never in the confirm form's own values.
|
|
63
|
-
export function SecretMintBody({ schema, screen, translate }: SecretMintBodyProps): ReactNode {
|
|
64
|
-
const nav = useNav();
|
|
65
|
-
const returnTarget = useReturnTarget(screen.id);
|
|
66
|
-
const { Card, Heading, Banner, Button, Text, Grid, GridCell, SecretReveal } = usePrimitives();
|
|
67
|
-
const t = useTranslation();
|
|
68
|
-
const effectiveTranslate = translate ?? t;
|
|
69
|
-
const synthEntity = useMemo(() => synthesizeActionFormEntity(screen.fields), [screen.fields]);
|
|
70
|
-
const synthScreen = useMemo(() => synthesizeActionFormScreen(screen), [screen]);
|
|
71
|
-
const handoffValues = useInitialValuesHandoff(screen.id);
|
|
72
|
-
const initial = useMemo(
|
|
73
|
-
() =>
|
|
74
|
-
mergeSearchParamsIntoInitial(screen.fields, {
|
|
75
|
-
searchParams: nav.searchParams,
|
|
76
|
-
urlPrefillFields: screen.urlPrefillFields,
|
|
77
|
-
renderableFields: layoutFieldNames(synthScreen),
|
|
78
|
-
// A mint form has no entity either, so its money fields name their own
|
|
79
|
-
// currency source (fw#2839). Only the literal form is resolved here:
|
|
80
|
-
// `{ kind: "tenant" }` would need the config query this screen never
|
|
81
|
-
// runs, and a one-time secret mint has no money use case to justify it.
|
|
82
|
-
moneyCurrencyOverrides: literalCurrencyOverrides(screen.fields),
|
|
83
|
-
...(handoffValues !== undefined && { handoffValues }),
|
|
84
|
-
}) as FormValues,
|
|
85
|
-
[screen.fields, screen.urlPrefillFields, nav.searchParams, synthScreen, handoffValues],
|
|
86
|
-
);
|
|
87
|
-
const [revealed, setRevealed] = useState<Readonly<Record<string, unknown>> | null>(null);
|
|
88
|
-
const [done, setDone] = useState(false);
|
|
89
|
-
// Never rendered, never merged into `revealed` or the confirm form's
|
|
90
|
-
// initial values — read only from `buildPayload` at confirm-submit time.
|
|
91
|
-
const carriedRef = useRef<Readonly<Record<string, unknown>>>({});
|
|
92
|
-
|
|
93
|
-
const confirm = screen.confirm;
|
|
94
|
-
const confirmEntity = useMemo(
|
|
95
|
-
() => (confirm !== undefined ? synthesizeActionFormEntity(confirm.fields) : undefined),
|
|
96
|
-
[confirm],
|
|
97
|
-
);
|
|
98
|
-
const confirmScreen = useMemo(
|
|
99
|
-
() => (confirm !== undefined ? synthesizeSecretMintConfirmScreen(screen, confirm) : undefined),
|
|
100
|
-
[screen, confirm],
|
|
101
|
-
);
|
|
102
|
-
const confirmInitial = useMemo(
|
|
103
|
-
() =>
|
|
104
|
-
confirm !== undefined
|
|
105
|
-
? (buildInitialValues(
|
|
106
|
-
confirm.fields,
|
|
107
|
-
undefined,
|
|
108
|
-
literalCurrencyOverrides(confirm.fields),
|
|
109
|
-
) as FormValues)
|
|
110
|
-
: undefined,
|
|
111
|
-
[confirm],
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
const handleSubmitted = useCallback(
|
|
115
|
-
(result: SubmitResult<unknown>) => {
|
|
116
|
-
if (!result.isSuccess) return;
|
|
117
|
-
const values: Record<string, unknown> = {};
|
|
118
|
-
for (const revealField of screen.reveal.fields) {
|
|
119
|
-
const value = extractRevealValue(result.data, revealField.field);
|
|
120
|
-
if (value === undefined) continue;
|
|
121
|
-
values[revealField.field] = value;
|
|
122
|
-
}
|
|
123
|
-
setRevealed(values);
|
|
124
|
-
carriedRef.current =
|
|
125
|
-
confirm?.carry !== undefined ? extractCarriedValues(result.data, confirm.carry) : {};
|
|
126
|
-
},
|
|
127
|
-
[screen.reveal.fields, confirm?.carry],
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const handleCancel = useMemo<(() => void) | undefined>(() => {
|
|
131
|
-
const target = screen.cancelTarget ?? screen.redirect;
|
|
132
|
-
if (target === undefined || target === false) return undefined;
|
|
133
|
-
return () =>
|
|
134
|
-
navigateToReturnOr(nav, returnTarget, () => nav.navigate({ screenId: lastSegment(target) }));
|
|
135
|
-
}, [nav, screen.redirect, screen.cancelTarget, returnTarget]);
|
|
136
|
-
|
|
137
|
-
// Ends the reveal phase for both paths (the bare acknowledge button, and a
|
|
138
|
-
// successful confirm submit): clears the secret and the carried values, then
|
|
139
|
-
// either navigates (screen.redirect) or shows a done-state — never falls
|
|
140
|
-
// back to re-rendering the mint form, which would let a stray click mint
|
|
141
|
-
// (and invalidate) the secret again.
|
|
142
|
-
const finishMint = useCallback(() => {
|
|
143
|
-
setRevealed(null);
|
|
144
|
-
carriedRef.current = {};
|
|
145
|
-
const redirect = screen.redirect;
|
|
146
|
-
if (redirect !== undefined) {
|
|
147
|
-
navigateToReturnOr(nav, returnTarget, () =>
|
|
148
|
-
nav.navigate({ screenId: lastSegment(redirect) }),
|
|
149
|
-
);
|
|
150
|
-
} else {
|
|
151
|
-
setDone(true);
|
|
152
|
-
}
|
|
153
|
-
}, [nav, screen.redirect, returnTarget]);
|
|
154
|
-
|
|
155
|
-
const handleConfirmSubmitted = useCallback(
|
|
156
|
-
(result: SubmitResult<unknown>) => {
|
|
157
|
-
if (result.isSuccess) finishMint();
|
|
158
|
-
},
|
|
159
|
-
[finishMint],
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
if (done) {
|
|
163
|
-
return (
|
|
164
|
-
<Card>
|
|
165
|
-
<Banner variant="info" testId="kumiko-screen-secret-mint-done">
|
|
166
|
-
{effectiveTranslate(confirm?.doneMessage ?? "kumiko.secretMint.done")}
|
|
167
|
-
</Banner>
|
|
168
|
-
</Card>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (revealed !== null) {
|
|
173
|
-
const values = screen.reveal.fields.flatMap((revealField) => {
|
|
174
|
-
const raw = revealed[revealField.field];
|
|
175
|
-
if (isBlank(raw)) return [];
|
|
176
|
-
const display = revealField.display ?? "code";
|
|
177
|
-
const value =
|
|
178
|
-
display === "list" && Array.isArray(raw)
|
|
179
|
-
? raw.map((v) => String(v)).join("\n")
|
|
180
|
-
: String(raw);
|
|
181
|
-
return [
|
|
182
|
-
{
|
|
183
|
-
label: effectiveTranslate(revealField.label),
|
|
184
|
-
value,
|
|
185
|
-
copyable: revealField.copyable ?? true,
|
|
186
|
-
multiline: display === "list",
|
|
187
|
-
...(display === "qr" && { qr: true }),
|
|
188
|
-
},
|
|
189
|
-
];
|
|
190
|
-
});
|
|
191
|
-
return (
|
|
192
|
-
<Card testId="kumiko-screen-secret-mint-card">
|
|
193
|
-
<Heading variant="page">
|
|
194
|
-
{effectiveTranslate(screen.reveal.title ?? "kumiko.secretMint.title")}
|
|
195
|
-
</Heading>
|
|
196
|
-
<Banner variant="warning" testId="kumiko-screen-secret-mint-warning">
|
|
197
|
-
{effectiveTranslate(screen.reveal.warning ?? "kumiko.secretMint.warning")}
|
|
198
|
-
</Banner>
|
|
199
|
-
{SecretReveal !== undefined ? (
|
|
200
|
-
<SecretReveal
|
|
201
|
-
values={values}
|
|
202
|
-
copyLabel={effectiveTranslate("kumiko.secretMint.copy")}
|
|
203
|
-
copiedLabel={effectiveTranslate("kumiko.secretMint.copied")}
|
|
204
|
-
testId="kumiko-screen-secret-mint-reveal"
|
|
205
|
-
/>
|
|
206
|
-
) : (
|
|
207
|
-
<Grid columns={1} testId="kumiko-screen-secret-mint-reveal">
|
|
208
|
-
{values.map((v) => (
|
|
209
|
-
<GridCell key={v.label}>
|
|
210
|
-
<Text>{v.label}</Text>
|
|
211
|
-
<Text variant="code">{v.value}</Text>
|
|
212
|
-
</GridCell>
|
|
213
|
-
))}
|
|
214
|
-
</Grid>
|
|
215
|
-
)}
|
|
216
|
-
{confirm !== undefined && confirmEntity !== undefined && confirmScreen !== undefined ? (
|
|
217
|
-
<RenderEdit
|
|
218
|
-
screen={confirmScreen}
|
|
219
|
-
entity={confirmEntity}
|
|
220
|
-
featureName={schema.featureName}
|
|
221
|
-
initial={confirmInitial ?? ({} as FormValues)}
|
|
222
|
-
writeCommand={confirm.handler}
|
|
223
|
-
payloadMode="values"
|
|
224
|
-
buildPayload={(snapshot) => ({ ...snapshot.values, ...carriedRef.current })}
|
|
225
|
-
onSubmit={handleConfirmSubmitted}
|
|
226
|
-
{...(handleCancel !== undefined && { onCancel: handleCancel })}
|
|
227
|
-
{...(translate !== undefined && { translate })}
|
|
228
|
-
{...(confirm.submitLabel !== undefined && { submitLabel: confirm.submitLabel })}
|
|
229
|
-
/>
|
|
230
|
-
) : (
|
|
231
|
-
<Button
|
|
232
|
-
type="button"
|
|
233
|
-
variant="primary"
|
|
234
|
-
onClick={finishMint}
|
|
235
|
-
testId="kumiko-screen-secret-mint-confirm"
|
|
236
|
-
>
|
|
237
|
-
{effectiveTranslate(screen.reveal.confirmLabel ?? "kumiko.secretMint.confirm")}
|
|
238
|
-
</Button>
|
|
239
|
-
)}
|
|
240
|
-
</Card>
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return (
|
|
245
|
-
<RenderEdit
|
|
246
|
-
screen={synthScreen}
|
|
247
|
-
entity={synthEntity}
|
|
248
|
-
featureName={schema.featureName}
|
|
249
|
-
initial={initial}
|
|
250
|
-
extensionInitialValues={initial}
|
|
251
|
-
writeCommand={screen.handler}
|
|
252
|
-
payloadMode="values"
|
|
253
|
-
onSubmit={handleSubmitted}
|
|
254
|
-
{...(handleCancel !== undefined && { onCancel: handleCancel })}
|
|
255
|
-
{...(translate !== undefined && { translate })}
|
|
256
|
-
{...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel })}
|
|
257
|
-
/>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import type { SecretsEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
-
import type { Translate } from "@cosmicdrift/kumiko-headless";
|
|
3
|
-
import { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
4
|
-
import { useDispatcher } from "../context/dispatcher-context";
|
|
5
|
-
import { useQuery } from "../hooks/use-query";
|
|
6
|
-
import { useTranslation } from "../i18n";
|
|
7
|
-
import { usePrimitives } from "../primitives";
|
|
8
|
-
import { dispatcherErrorText } from "./write-failed-error";
|
|
9
|
-
|
|
10
|
-
type SecretListRow = {
|
|
11
|
-
readonly key: string;
|
|
12
|
-
readonly redactedPreview: string | null;
|
|
13
|
-
readonly hint: string | null;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type SecretsEditBodyProps = {
|
|
17
|
-
readonly screen: SecretsEditScreenDefinition;
|
|
18
|
-
readonly translate?: Translate;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// secrets:query:list never returns plaintext (only a redacted preview), so
|
|
22
|
-
// unlike ConfigEditBody there is no server value to pre-fill a draft with —
|
|
23
|
-
// every input starts at "" and stays that way unless the user types into it.
|
|
24
|
-
export function SecretsEditBody({ screen, translate }: SecretsEditBodyProps): ReactNode {
|
|
25
|
-
const { Banner, Form, Section, Field, Input, Button, Text } = usePrimitives();
|
|
26
|
-
const t = useTranslation();
|
|
27
|
-
const effectiveTranslate = translate ?? t;
|
|
28
|
-
const dispatcher = useDispatcher();
|
|
29
|
-
const listQuery = useQuery<readonly SecretListRow[]>("secrets:query:list", {});
|
|
30
|
-
|
|
31
|
-
const [drafts, setDrafts] = useState<Readonly<Record<string, string>>>({});
|
|
32
|
-
const [submitting, setSubmitting] = useState(false);
|
|
33
|
-
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
34
|
-
|
|
35
|
-
const rowsByQualifiedKey = useMemo(() => {
|
|
36
|
-
const out = new Map<string, SecretListRow>();
|
|
37
|
-
for (const row of listQuery.data ?? []) out.set(row.key, row);
|
|
38
|
-
return out;
|
|
39
|
-
}, [listQuery.data]);
|
|
40
|
-
|
|
41
|
-
const setDraft = useCallback((fieldId: string, value: string) => {
|
|
42
|
-
setDrafts((prev) => ({ ...prev, [fieldId]: value }));
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
const handleSubmit = useCallback(async (): Promise<void> => {
|
|
46
|
-
const commands = Object.entries(screen.secretKeys).flatMap(([fieldId, qualified]) => {
|
|
47
|
-
const value = drafts[fieldId]?.trim();
|
|
48
|
-
return value ? [{ type: "secrets:write:set", payload: { key: qualified, value } }] : [];
|
|
49
|
-
});
|
|
50
|
-
if (commands.length === 0) return;
|
|
51
|
-
setSubmitting(true);
|
|
52
|
-
setSubmitError(null);
|
|
53
|
-
const result = await dispatcher.batch(commands);
|
|
54
|
-
setSubmitting(false);
|
|
55
|
-
if (!result.isSuccess) {
|
|
56
|
-
setSubmitError(dispatcherErrorText(result.error, effectiveTranslate));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
setDrafts({});
|
|
60
|
-
await listQuery.refetch();
|
|
61
|
-
}, [dispatcher, drafts, screen.secretKeys, listQuery.refetch, effectiveTranslate]);
|
|
62
|
-
|
|
63
|
-
const handleDelete = useCallback(
|
|
64
|
-
async (qualified: string): Promise<void> => {
|
|
65
|
-
const result = await dispatcher.write("secrets:write:delete", { key: qualified });
|
|
66
|
-
if (!result.isSuccess) {
|
|
67
|
-
setSubmitError(dispatcherErrorText(result.error, effectiveTranslate));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
await listQuery.refetch();
|
|
71
|
-
},
|
|
72
|
-
[dispatcher, listQuery.refetch, effectiveTranslate],
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
if (listQuery.loading && listQuery.data === null) {
|
|
76
|
-
return (
|
|
77
|
-
<Banner padded variant="loading" testId="kumiko-screen-loading">
|
|
78
|
-
Loading…
|
|
79
|
-
</Banner>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
if (listQuery.error) {
|
|
83
|
-
return (
|
|
84
|
-
<Banner padded variant="error" testId="kumiko-screen-error">
|
|
85
|
-
{dispatcherErrorText(listQuery.error, effectiveTranslate)}
|
|
86
|
-
</Banner>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<Form
|
|
92
|
-
onSubmit={() => {
|
|
93
|
-
void handleSubmit();
|
|
94
|
-
}}
|
|
95
|
-
testId="secrets-edit-form"
|
|
96
|
-
{...(screen.description !== undefined && {
|
|
97
|
-
subtitle: effectiveTranslate(screen.description),
|
|
98
|
-
})}
|
|
99
|
-
actions={
|
|
100
|
-
<Button
|
|
101
|
-
type="submit"
|
|
102
|
-
variant="primary"
|
|
103
|
-
loading={submitting}
|
|
104
|
-
disabled={submitting}
|
|
105
|
-
testId="secrets-edit-submit"
|
|
106
|
-
>
|
|
107
|
-
{effectiveTranslate("kumiko.actions.save")}
|
|
108
|
-
</Button>
|
|
109
|
-
}
|
|
110
|
-
>
|
|
111
|
-
{submitError !== null && (
|
|
112
|
-
<Banner variant="error" testId="secrets-edit-error">
|
|
113
|
-
{submitError}
|
|
114
|
-
</Banner>
|
|
115
|
-
)}
|
|
116
|
-
{screen.sections.map((section, index) => (
|
|
117
|
-
<Section
|
|
118
|
-
key={section.title ?? `section-${index}`}
|
|
119
|
-
{...(section.title !== undefined && { title: effectiveTranslate(section.title) })}
|
|
120
|
-
>
|
|
121
|
-
{/* kumiko-lint-ignore section-fields-raw secretsEdit section fields are field-id strings, not EditFieldSpec */}
|
|
122
|
-
{section.fields.map((fieldId) => {
|
|
123
|
-
const qualified = screen.secretKeys[fieldId];
|
|
124
|
-
if (qualified === undefined) return null;
|
|
125
|
-
const row = rowsByQualifiedKey.get(qualified);
|
|
126
|
-
const hintKey = screen.fieldHints?.[fieldId];
|
|
127
|
-
const isRequired = screen.requiredFields?.includes(fieldId) ?? false;
|
|
128
|
-
return (
|
|
129
|
-
<Field
|
|
130
|
-
key={fieldId}
|
|
131
|
-
id={fieldId}
|
|
132
|
-
label={effectiveTranslate(screen.fieldLabels[fieldId] ?? fieldId)}
|
|
133
|
-
required={isRequired}
|
|
134
|
-
testId={`field-${fieldId}`}
|
|
135
|
-
fieldAppendix={
|
|
136
|
-
<>
|
|
137
|
-
{hintKey !== undefined && (
|
|
138
|
-
<Text variant="small">{effectiveTranslate(hintKey)}</Text>
|
|
139
|
-
)}
|
|
140
|
-
{row !== undefined ? (
|
|
141
|
-
<>
|
|
142
|
-
<Text variant="small" testId={`secret-preview-${fieldId}`}>
|
|
143
|
-
{row.redactedPreview ?? effectiveTranslate("config.secrets.set")}
|
|
144
|
-
</Text>
|
|
145
|
-
<Button
|
|
146
|
-
type="button"
|
|
147
|
-
variant="danger-ghost"
|
|
148
|
-
size="sm"
|
|
149
|
-
onClick={() => handleDelete(qualified)}
|
|
150
|
-
testId={`secret-delete-${fieldId}`}
|
|
151
|
-
>
|
|
152
|
-
{effectiveTranslate("config.secrets.delete")}
|
|
153
|
-
</Button>
|
|
154
|
-
</>
|
|
155
|
-
) : (
|
|
156
|
-
<>
|
|
157
|
-
<Text variant="small" testId={`secret-not-set-${fieldId}`}>
|
|
158
|
-
{effectiveTranslate("config.secrets.notSet")}
|
|
159
|
-
</Text>
|
|
160
|
-
{isRequired && (
|
|
161
|
-
<Text variant="small" testId={`required-marker-${fieldId}`}>
|
|
162
|
-
{effectiveTranslate("config.secrets.required")}
|
|
163
|
-
</Text>
|
|
164
|
-
)}
|
|
165
|
-
</>
|
|
166
|
-
)}
|
|
167
|
-
</>
|
|
168
|
-
}
|
|
169
|
-
>
|
|
170
|
-
<Text variant="small">
|
|
171
|
-
{effectiveTranslate(
|
|
172
|
-
row !== undefined
|
|
173
|
-
? "config.secrets.replacePlaceholder"
|
|
174
|
-
: "config.secrets.placeholder",
|
|
175
|
-
)}
|
|
176
|
-
</Text>
|
|
177
|
-
<Input
|
|
178
|
-
kind="password"
|
|
179
|
-
id={fieldId}
|
|
180
|
-
name={fieldId}
|
|
181
|
-
value={drafts[fieldId] ?? ""}
|
|
182
|
-
onChange={(v) => setDraft(fieldId, v)}
|
|
183
|
-
autoComplete="new-password"
|
|
184
|
-
disabled={submitting}
|
|
185
|
-
testId={`secret-input-${fieldId}`}
|
|
186
|
-
/>
|
|
187
|
-
</Field>
|
|
188
|
-
);
|
|
189
|
-
})}
|
|
190
|
-
</Section>
|
|
191
|
-
))}
|
|
192
|
-
</Form>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { useUserRoles } from "../context/user-roles-context";
|
|
2
|
-
import { useAppFeatures } from "./app-features-context";
|
|
3
|
-
import type { FeatureSchema } from "./feature-schema";
|
|
4
|
-
import { featureNameFromQualifiedScreenId, qualifyScreenId } from "./qualify-screen-id";
|
|
5
|
-
import { screenAccessAllows } from "./screen-access";
|
|
6
|
-
|
|
7
|
-
export type EmbeddedScreenTarget = {
|
|
8
|
-
readonly schema: FeatureSchema;
|
|
9
|
-
readonly qn: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// Undefined (not KumikoScreen's access-denied banner) so the host can drop the whole tile.
|
|
13
|
-
export function useEmbeddedScreen(
|
|
14
|
-
hostFeatureName: string,
|
|
15
|
-
screen: string,
|
|
16
|
-
): EmbeddedScreenTarget | undefined {
|
|
17
|
-
const features = useAppFeatures();
|
|
18
|
-
const userRoles = useUserRoles();
|
|
19
|
-
const crossFeatureName = featureNameFromQualifiedScreenId(screen);
|
|
20
|
-
const targetFeatureName = crossFeatureName ?? hostFeatureName;
|
|
21
|
-
const qn = crossFeatureName !== undefined ? screen : qualifyScreenId(hostFeatureName, screen);
|
|
22
|
-
const schema = features.find((f) => f.featureName === targetFeatureName);
|
|
23
|
-
const target = schema?.screens.find((s) => qualifyScreenId(targetFeatureName, s.id) === qn);
|
|
24
|
-
if (schema === undefined || target === undefined) return undefined;
|
|
25
|
-
if (!screenAccessAllows(target.access, userRoles)) return undefined;
|
|
26
|
-
return { schema, qn };
|
|
27
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// Editor-agnostic chip bar for a content editor's declared variables
|
|
2
|
-
// (ContentCollectionDefinition.variableSchema). Renders next to the text
|
|
3
|
-
// surface, never inside it — step 3 (rich/tiptap) reuses this same bar over
|
|
4
|
-
// a different editor, so it must not assume a textarea underneath.
|
|
5
|
-
|
|
6
|
-
import { Fragment, type ReactNode } from "react";
|
|
7
|
-
import { useTranslation } from "../i18n";
|
|
8
|
-
import { usePrimitives } from "../primitives";
|
|
9
|
-
|
|
10
|
-
export type VariableChipsProps = {
|
|
11
|
-
readonly variables: readonly string[];
|
|
12
|
-
readonly onInsert: (name: string) => void;
|
|
13
|
-
readonly disabled?: boolean;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// No wrapping element: @cosmicdrift/kumiko-renderer is DOM-free (native impls
|
|
17
|
-
// have no <div>), and layout is a platform concern anyway — the caller
|
|
18
|
-
// (PlainContentEditor et al.) places these buttons in its own container.
|
|
19
|
-
export function VariableChips({ variables, onInsert, disabled }: VariableChipsProps): ReactNode {
|
|
20
|
-
const { Button } = usePrimitives();
|
|
21
|
-
const t = useTranslation();
|
|
22
|
-
return (
|
|
23
|
-
<Fragment>
|
|
24
|
-
{variables.map((name) => (
|
|
25
|
-
<Button
|
|
26
|
-
key={name}
|
|
27
|
-
type="button"
|
|
28
|
-
variant="secondary"
|
|
29
|
-
size="sm"
|
|
30
|
-
disabled={disabled}
|
|
31
|
-
onClick={() => onInsert(name)}
|
|
32
|
-
ariaLabel={t("kumiko.contentEditor.insertVariable", { name })}
|
|
33
|
-
>
|
|
34
|
-
{`{{${name}}}`}
|
|
35
|
-
</Button>
|
|
36
|
-
))}
|
|
37
|
-
</Fragment>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { DispatcherError } from "@cosmicdrift/kumiko-headless";
|
|
2
|
-
|
|
3
|
-
// dispatcher.write wirft bei Server-Fehlern NICHT — es returnt
|
|
4
|
-
// { isSuccess: false, error }. Action-Wiring das das Result verwirft,
|
|
5
|
-
// macht Fehler unsichtbar ("Klick tut nichts"-Prod-Bug 2026-06-07).
|
|
6
|
-
// Diese Klasse trägt den strukturierten DispatcherError dahin, wo eine
|
|
7
|
-
// UI ihn anzeigen kann (Toast mit docsUrl).
|
|
8
|
-
export class WriteFailedError extends Error {
|
|
9
|
-
readonly dispatcherError: DispatcherError;
|
|
10
|
-
|
|
11
|
-
constructor(error: DispatcherError, message: string) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.name = "WriteFailedError";
|
|
14
|
-
this.dispatcherError = error;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// i18nKey gewinnt wenn das Bundle ihn kennt (translate returnt den Key
|
|
19
|
-
// unverändert wenn nicht — Renderer-Convention), sonst message, zuletzt code.
|
|
20
|
-
export function dispatcherErrorText(
|
|
21
|
-
error: DispatcherError,
|
|
22
|
-
translate: (key: string, params?: Readonly<Record<string, unknown>>) => string,
|
|
23
|
-
): string {
|
|
24
|
-
const translated = translate(error.i18nKey, error.i18nParams);
|
|
25
|
-
if (translated !== error.i18nKey) return translated;
|
|
26
|
-
return error.message !== "" ? error.message : error.code;
|
|
27
|
-
}
|