@cosmicdrift/kumiko-samples 0.393.0 → 0.394.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.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +15 -5
- package/packages/bundled-features/src/account-security/README.md +1 -1
- package/packages/bundled-features/src/account-security/changes.json +6 -0
- package/packages/bundled-features/src/account-security/feature.ts +9 -1
- package/packages/bundled-features/src/audit/event-classification.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +10 -52
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +3 -12
- package/packages/bundled-features/src/auth-email-password/web/index.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/login-failure.ts +54 -0
- package/packages/bundled-features/src/auth-email-password/web/login-url.ts +19 -0
- package/packages/bundled-features/src/auth-email-password/web/static-login.ts +183 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +7 -63
- package/packages/bundled-features/src/billing-foundation/changes.json +30 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +8 -16
- package/packages/bundled-features/src/billing-foundation/events.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +9 -2
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/one-off-checkout-gate.ts +44 -0
- package/packages/bundled-features/src/billing-foundation/one-off-customer.ts +17 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/types.ts +26 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +1 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +8 -2
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-recurring.ts +23 -0
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -0
- package/packages/bundled-features/src/cap-overview/changes.json +6 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +3 -3
- package/packages/bundled-features/src/cap-overview/types.ts +2 -0
- package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +10 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +12 -3
- package/packages/bundled-features/src/files/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-health.ts +146 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll-schedule.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll.ts +238 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-record.ts +2 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-wake.ts +21 -0
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +143 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-callback-verify.ts +65 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-route-responses.ts +26 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +93 -62
- package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +66 -3
- package/packages/bundled-features/src/inbound-mail-foundation/content-refetch.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/describe-error.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +45 -2
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +69 -2
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +133 -118
- package/packages/bundled-features/src/inbound-mail-foundation/file-storage-failure.ts +23 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-address.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/acknowledge-quarantine.write.ts +55 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/attach-message-content.write.ts +184 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +137 -84
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +65 -45
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-prepare.ts +177 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-schema.ts +42 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +79 -160
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +93 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +46 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-quarantine.query.ts +106 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/mark-credential-stored.write.ts +47 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.query.ts +99 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.ts +229 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-detail.query.ts +34 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/reactivate-account.write.ts +129 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/retry-quarantined-message.write.ts +49 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +48 -9
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/set-trusted-authserv-id.write.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/thread-rollup.ts +136 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/trusted-authserv-id.ts +4 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +82 -60
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +80 -2
- package/packages/bundled-features/src/inbound-mail-foundation/inbound-oauth-flow.ts +48 -0
- package/packages/bundled-features/src/inbound-mail-foundation/index.ts +93 -15
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-failures.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-payload.ts +46 -0
- package/packages/bundled-features/src/inbound-mail-foundation/mail-roles.ts +132 -0
- package/packages/bundled-features/src/inbound-mail-foundation/normalize-inbound-message.ts +119 -0
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +26 -8
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachment-access.ts +28 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachments.ts +111 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-mail.ts +19 -1
- package/packages/bundled-features/src/inbound-mail-foundation/probe.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +33 -2
- package/packages/bundled-features/src/inbound-mail-foundation/provider-errors.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-entities.ts +56 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-header-import.ts +63 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-retry.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-screens.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine.ts +72 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-job.ts +78 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +43 -8
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +238 -89
- package/packages/bundled-features/src/inbound-mail-foundation/send-from-account.ts +24 -5
- package/packages/bundled-features/src/inbound-mail-foundation/sender-auth.ts +215 -0
- package/packages/bundled-features/src/inbound-mail-foundation/supervisor-ingest.ts +311 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-account-now.ts +61 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-cursor-store.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/syncable-accounts.ts +123 -0
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -1
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +76 -105
- package/packages/bundled-features/src/inbound-mail-foundation/wake-consumer.ts +33 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-catch-up.ts +68 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-lease.ts +124 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor-types.ts +87 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +143 -433
- package/packages/bundled-features/src/inbound-mail-foundation/web/client-plugin.ts +73 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/constants.ts +7 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/index.ts +5 -0
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +30 -0
- package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +124 -41
- package/packages/bundled-features/src/inbound-provider-imap/connect-my-mailbox.ts +45 -0
- package/packages/bundled-features/src/inbound-provider-imap/constants.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/download-batch.ts +38 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +75 -49
- package/packages/bundled-features/src/inbound-provider-imap/fetch-content.ts +84 -0
- package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +9 -0
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +35 -84
- package/packages/bundled-features/src/inbound-provider-imap/index.ts +1 -2
- package/packages/bundled-features/src/inbound-provider-imap/mime-content.ts +85 -0
- package/packages/bundled-features/src/inbound-provider-imap/parse-message.ts +149 -0
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-account.ts +4 -6
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-mailbox.ts +75 -45
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-prefill.query.ts +19 -8
- package/packages/bundled-features/src/inbound-provider-imap/screens.ts +203 -109
- package/packages/bundled-features/src/inbound-provider-imap/send-mail.ts +11 -2
- package/packages/bundled-features/src/inbound-provider-imap/sender-auth-headers.ts +36 -0
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +75 -3
- package/packages/bundled-features/src/inbound-provider-inmemory/index.ts +6 -0
- package/packages/bundled-features/src/jobs/changes.json +18 -0
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -0
- package/packages/bundled-features/src/subscription-paddle/changes.json +15 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-config.query.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-methods.ts +107 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-page.ts +154 -0
- package/packages/bundled-features/src/subscription-paddle/client.ts +78 -0
- package/packages/bundled-features/src/subscription-paddle/constants.ts +32 -0
- package/packages/bundled-features/src/subscription-paddle/feature.ts +265 -0
- package/packages/bundled-features/src/subscription-paddle/index.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/plan-switch-methods.ts +135 -0
- package/packages/bundled-features/src/subscription-paddle/price-methods.ts +148 -0
- package/packages/bundled-features/src/subscription-paddle/runtime.ts +203 -0
- package/packages/bundled-features/src/subscription-paddle/subscription-state.ts +73 -0
- package/packages/bundled-features/src/subscription-paddle/tenant-binding.ts +34 -0
- package/packages/bundled-features/src/subscription-paddle/verify-webhook.ts +61 -0
- package/packages/bundled-features/src/subscription-paddle/webhook-events.ts +172 -0
- package/packages/bundled-features/src/template-resolver/changes.json +12 -0
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +25 -0
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +218 -117
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +1 -2
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +2 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +4 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +11 -5
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +2 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +87 -38
- package/samples/apps/use-all-bundled/kumiko/migrations/0056_inbound-mail-quarantine-retry.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +1 -0
- package/samples/apps/use-all-bundled/playwright.billing-docs.config.ts +15 -0
- package/samples/apps/use-all-bundled/src/app/cap-overview-caps.ts +16 -2
- package/samples/apps/use-all-bundled/src/app/cap-overview-labels-feature.ts +2 -1
- package/samples/apps/use-all-bundled/src/app/client.tsx +2 -0
- package/samples/apps/use-all-bundled/src/app/screens-feature.ts +14 -2
- package/samples/apps/use-all-bundled/src/app/seed.ts +4 -1
- package/samples/apps/use-all-bundled/src/run-config.ts +7 -5
- package/samples/recipes/cross-feature-events/README.md +10 -0
- package/samples/recipes/cross-feature-events/src/feature.ts +14 -0
- package/samples/recipes/dashboard-stats/README.md +8 -0
- package/samples/recipes/dashboard-stats/src/feature.ts +91 -1
- package/samples/recipes/env-composition/README.md +2 -2
- package/samples/recipes/env-composition/src/env.ts +2 -2
- package/samples/recipes/inbound-mail-triage/README.md +8 -0
- package/samples/recipes/pdf-audit-export/src/feature.ts +71 -48
- package/packages/bundled-features/src/subscription-dodo/changes.json +0 -14
- package/packages/bundled-features/src/subscription-dodo/client.ts +0 -45
- package/packages/bundled-features/src/subscription-dodo/constants.ts +0 -24
- package/packages/bundled-features/src/subscription-dodo/feature.ts +0 -207
- package/packages/bundled-features/src/subscription-dodo/index.ts +0 -16
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +0 -345
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +0 -126
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +0 -262
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
TreeChildrenSubscribe,
|
|
17
17
|
TreeNode,
|
|
18
18
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
19
|
+
import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
19
20
|
import {
|
|
20
21
|
ContentPreview,
|
|
21
22
|
type FeatureSchema,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
} from "@cosmicdrift/kumiko-renderer/web";
|
|
35
36
|
import type { ContentEditorFormat } from "@cosmicdrift/kumiko-types/nav";
|
|
36
37
|
import { type FormEvent, type ReactNode, useEffect, useId, useState } from "react";
|
|
38
|
+
import { DEFAULT_COLLECTION_ACCESS } from "./constants.ts";
|
|
37
39
|
import { DocumentBlocksContext, DocumentContentEditor } from "./document-editor.tsx";
|
|
38
40
|
import { documentBlocksForKind } from "./document-toolbar-blocks.ts";
|
|
39
41
|
import { defaultTranslations } from "./i18n.ts";
|
|
@@ -155,57 +157,138 @@ export function groupBlocksByFolder(
|
|
|
155
157
|
];
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
// A save in the editor re-fires the tree of the collection it wrote to. The
|
|
161
|
+
// SSE refresh does not cover every collection: per-user entries carry no
|
|
162
|
+
// entity signal, because naming their ids to every collection reader would
|
|
163
|
+
// reveal when colleagues change their own entries.
|
|
164
|
+
const TEXT_BLOCK_TREE_KEY = "";
|
|
165
|
+
const treeRefreshListeners = new Map<string, Set<() => void>>();
|
|
166
|
+
|
|
167
|
+
function onTreeRefresh(treeKey: string, listener: () => void): () => void {
|
|
168
|
+
const listeners = treeRefreshListeners.get(treeKey) ?? new Set();
|
|
169
|
+
listeners.add(listener);
|
|
170
|
+
treeRefreshListeners.set(treeKey, listeners);
|
|
171
|
+
return () => listeners.delete(listener);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function refreshTree(treeKey: string): void {
|
|
175
|
+
for (const listener of treeRefreshListeners.get(treeKey) ?? []) listener();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type BlockListResult =
|
|
179
|
+
| { readonly ok: true; readonly blocks: readonly BlockSummary[] }
|
|
180
|
+
| { readonly ok: false; readonly status: number; readonly body: string };
|
|
181
|
+
|
|
182
|
+
async function postBlockList(
|
|
183
|
+
queryType: string,
|
|
184
|
+
tenantIdOverride: string | undefined,
|
|
185
|
+
): Promise<BlockListResult> {
|
|
186
|
+
// CSRF header is mandatory on authenticated requests (auth-middleware
|
|
187
|
+
// double-submit pattern). Anonymous/pre-login has no token → header
|
|
188
|
+
// omitted → the server takes the anonymous path.
|
|
189
|
+
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
190
|
+
const csrf = readCsrfToken();
|
|
191
|
+
if (csrf !== undefined) headers[CSRF_HEADER_NAME] = csrf;
|
|
192
|
+
// kumiko-lint-ignore no-raw-hooks not yet converted to the framework hook set
|
|
193
|
+
const response = await fetch("/api/query", {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers,
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
type: queryType,
|
|
198
|
+
payload: tenantIdOverride !== undefined ? { tenantIdOverride } : {},
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
return {
|
|
203
|
+
ok: false,
|
|
204
|
+
status: response.status,
|
|
205
|
+
body: await response.text().catch(() => response.statusText),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const data: ByTenantResponse = await response.json();
|
|
209
|
+
return { ok: true, blocks: data.data.blocks };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const TENANT_OVERRIDE_DENIED_REASON = "tenant_override_requires_system_admin";
|
|
213
|
+
|
|
214
|
+
// The dispatcher accepts tenantIdOverride from a SystemAdmin only, and the
|
|
215
|
+
// provider cannot see the session roles. Everyone else gets this exact denial
|
|
216
|
+
// and reads their session tenant instead, which is the only tenant they may
|
|
217
|
+
// edit anyway.
|
|
218
|
+
function isTenantOverrideDenied(result: BlockListResult): boolean {
|
|
219
|
+
return !result.ok && result.status === 403 && result.body.includes(TENANT_OVERRIDE_DENIED_REASON);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function loadBlocks(
|
|
223
|
+
queryType: string,
|
|
224
|
+
tenantIdOverride: string | undefined,
|
|
225
|
+
): Promise<{ readonly blocks: readonly BlockSummary[]; readonly tenantIdOverride?: string }> {
|
|
226
|
+
let result = await postBlockList(queryType, tenantIdOverride);
|
|
227
|
+
let effectiveOverride = tenantIdOverride;
|
|
228
|
+
if (tenantIdOverride !== undefined && isTenantOverrideDenied(result)) {
|
|
229
|
+
result = await postBlockList(queryType, undefined);
|
|
230
|
+
effectiveOverride = undefined;
|
|
231
|
+
}
|
|
232
|
+
if (!result.ok) {
|
|
233
|
+
// 403 (CSRF/auth) or 5xx — explicit error instead of silent empty.
|
|
234
|
+
throw new Error(`text-block load failed: ${result.status} ${result.body}`);
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
blocks: result.blocks,
|
|
238
|
+
...(effectiveOverride !== undefined && { tenantIdOverride: effectiveOverride }),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
158
242
|
// tenantIdOverride (SystemAdmin-only) lets an app point the content tree at a
|
|
159
243
|
// tenant other than the session's — publicstatus seeds marketing/legal blocks
|
|
160
244
|
// on SYSTEM_TENANT_ID, so its SystemAdmin must read that tenant, not their own.
|
|
161
|
-
// Without a collection
|
|
245
|
+
// Without a collection: the anonymous-capable by-tenant query, so a public
|
|
162
246
|
// page keeps its content sidebar without a session. With one: that
|
|
163
247
|
// collection's own list handler, which carries the collection's access rule.
|
|
164
|
-
function makeTreeProvider(
|
|
248
|
+
function makeTreeProvider(
|
|
249
|
+
tenantIdOverride?: string,
|
|
250
|
+
collection?: Pick<ContentCollectionSchema, "id" | "ownership">,
|
|
251
|
+
): TreeChildrenSubscribe {
|
|
252
|
+
const collectionId = collection?.id;
|
|
165
253
|
const queryType =
|
|
166
254
|
collectionId === undefined
|
|
167
255
|
? TemplateResolverQueries.byTenant
|
|
168
256
|
: collectionQueryName(collectionId, "list");
|
|
257
|
+
// A per-user entry belongs to its author in the session tenant; pointing the
|
|
258
|
+
// tree at another tenant would only hide the author's own entries.
|
|
259
|
+
const requestedOverride = collection?.ownership === "user" ? undefined : tenantIdOverride;
|
|
169
260
|
return () => (emit, emitError) => {
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// plus retry. Falls back to emit([]) for consumers without emitError.
|
|
202
|
-
if (emitError) {
|
|
203
|
-
emitError(e instanceof Error ? e : new Error(String(e)));
|
|
204
|
-
} else {
|
|
205
|
-
emit([]);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
return () => {};
|
|
261
|
+
let active = true;
|
|
262
|
+
// Each denial logs a client-fault warning on the server, so a reload after a
|
|
263
|
+
// save does not ask again once this subscription learned the answer.
|
|
264
|
+
let override = requestedOverride;
|
|
265
|
+
const load = (): void => {
|
|
266
|
+
loadBlocks(queryType, override)
|
|
267
|
+
.then(({ blocks, tenantIdOverride: effectiveOverride }) => {
|
|
268
|
+
if (!active) return;
|
|
269
|
+
override = effectiveOverride;
|
|
270
|
+
// The app-side r.nav node IS the "Content" container → the provider's
|
|
271
|
+
// children are the folders/leaves below it, not the wrapper.
|
|
272
|
+
const content = groupBlocksByFolder(blocks, effectiveOverride, collectionId)[0];
|
|
273
|
+
emit(content !== undefined && Array.isArray(content.children) ? content.children : []);
|
|
274
|
+
})
|
|
275
|
+
.catch((e) => {
|
|
276
|
+
if (!active) return;
|
|
277
|
+
// Explicit error signal via emitError; ProviderBranch shows a banner
|
|
278
|
+
// plus retry. Falls back to emit([]) for consumers without emitError.
|
|
279
|
+
if (emitError) {
|
|
280
|
+
emitError(e instanceof Error ? e : new Error(String(e)));
|
|
281
|
+
} else {
|
|
282
|
+
emit([]);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
load();
|
|
287
|
+
const unsubscribe = onTreeRefresh(collectionId ?? TEXT_BLOCK_TREE_KEY, load);
|
|
288
|
+
return () => {
|
|
289
|
+
active = false;
|
|
290
|
+
unsubscribe();
|
|
291
|
+
};
|
|
209
292
|
};
|
|
210
293
|
}
|
|
211
294
|
|
|
@@ -243,10 +326,19 @@ function findVariableExamples(
|
|
|
243
326
|
return findCollection(features, collectionId)?.variableSchema ?? {};
|
|
244
327
|
}
|
|
245
328
|
|
|
246
|
-
// Edit form: loads the current values
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
// TenantAdmin
|
|
329
|
+
// Edit form: loads the current values, dispatches set on submit. Who may save
|
|
330
|
+
// follows the server: a collection's set handler carries the collection's own
|
|
331
|
+
// access rule (one rule for list, item and set), the public text-block pair
|
|
332
|
+
// stays TenantAdmin/SystemAdmin. Other users see the form read-only.
|
|
333
|
+
export function canWriteContent(
|
|
334
|
+
collection: Pick<ContentCollectionSchema, "access"> | undefined,
|
|
335
|
+
roles: readonly string[] | undefined,
|
|
336
|
+
): boolean {
|
|
337
|
+
if (collection !== undefined) {
|
|
338
|
+
return isUiAccessGranted(collection.access ?? DEFAULT_COLLECTION_ACCESS, roles);
|
|
339
|
+
}
|
|
340
|
+
return roles?.includes("TenantAdmin") === true || roles?.includes("SystemAdmin") === true;
|
|
341
|
+
}
|
|
250
342
|
|
|
251
343
|
type TextBlock = {
|
|
252
344
|
readonly slug: string;
|
|
@@ -303,7 +395,7 @@ function TextBlockEditor({
|
|
|
303
395
|
// which is what the hand-wired content tree uses.
|
|
304
396
|
const collectionId = args?.collectionId;
|
|
305
397
|
|
|
306
|
-
const { Form, Field, Input, Button, Banner } = usePrimitives();
|
|
398
|
+
const { Form, Field, Input, Button, Banner, PageHeader } = usePrimitives();
|
|
307
399
|
const dispatcher = useDispatcher();
|
|
308
400
|
const user = useShellUser();
|
|
309
401
|
const t = useTranslation();
|
|
@@ -312,10 +404,10 @@ function TextBlockEditor({
|
|
|
312
404
|
const contentFormat = findContentFormat(features, collectionId);
|
|
313
405
|
const variableExamples = findVariableExamples(features, collectionId);
|
|
314
406
|
const variables = Object.keys(variableExamples);
|
|
315
|
-
const
|
|
407
|
+
const collection = findCollection(features, collectionId);
|
|
408
|
+
const documentBlocks = documentBlocksForKind(collection?.kind);
|
|
316
409
|
const ContentEditor = useContentEditor(contentFormat);
|
|
317
|
-
const canWrite =
|
|
318
|
-
user?.roles.includes("TenantAdmin") === true || user?.roles.includes("SystemAdmin") === true;
|
|
410
|
+
const canWrite = canWriteContent(collection, user?.roles);
|
|
319
411
|
|
|
320
412
|
// Same split as the tree provider: a node without a collection reads through
|
|
321
413
|
// the public by-slug, a collection node through that collection's handlers.
|
|
@@ -369,6 +461,7 @@ function TextBlockEditor({
|
|
|
369
461
|
...(tenantIdOverride !== undefined && { tenantIdOverride }),
|
|
370
462
|
});
|
|
371
463
|
if (result.isSuccess) {
|
|
464
|
+
refreshTree(collectionId ?? TEXT_BLOCK_TREE_KEY);
|
|
372
465
|
setSavedMsg(
|
|
373
466
|
result.data.isNew
|
|
374
467
|
? t("template-resolver.editor.created")
|
|
@@ -394,76 +487,83 @@ function TextBlockEditor({
|
|
|
394
487
|
};
|
|
395
488
|
|
|
396
489
|
const disabled = submitting || loading || !canWrite;
|
|
490
|
+
const headerTitle = title || slug || undefined;
|
|
397
491
|
|
|
398
492
|
return (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
{t("template-resolver.editor.loadFailed")}: {loadError.code}
|
|
416
|
-
</Banner>
|
|
417
|
-
)}
|
|
418
|
-
{!canWrite && !loading && (
|
|
419
|
-
<Banner variant="info">{t("template-resolver.editor.readOnly")}</Banner>
|
|
420
|
-
)}
|
|
421
|
-
<Field id="text-block-title" label={t("template-resolver.editor.titleLabel")} required>
|
|
422
|
-
<Input
|
|
423
|
-
kind="text"
|
|
424
|
-
id="text-block-title"
|
|
425
|
-
name="text-block-title"
|
|
426
|
-
value={title}
|
|
427
|
-
onChange={setTitle}
|
|
428
|
-
disabled={disabled}
|
|
429
|
-
required
|
|
430
|
-
/>
|
|
431
|
-
</Field>
|
|
432
|
-
<Field
|
|
433
|
-
id={contentEditorId}
|
|
434
|
-
label={t("template-resolver.editor.contentLabel")}
|
|
435
|
-
labelAppendix={
|
|
436
|
-
<Button
|
|
437
|
-
type="button"
|
|
438
|
-
variant="secondary"
|
|
439
|
-
size="sm"
|
|
440
|
-
onClick={() => setPreviewing((p) => !p)}
|
|
441
|
-
>
|
|
442
|
-
{previewing ? t("kumiko.contentEditor.editMode") : t("kumiko.contentEditor.preview")}
|
|
443
|
-
</Button>
|
|
493
|
+
<>
|
|
494
|
+
{/* The editor opens beside the routed screen, so without this the shell header keeps that screen's title. */}
|
|
495
|
+
{PageHeader !== undefined && headerTitle !== undefined && <PageHeader title={headerTitle} />}
|
|
496
|
+
<Form
|
|
497
|
+
onSubmit={onSubmit}
|
|
498
|
+
testId="text-block-editor"
|
|
499
|
+
title={title || slug || EMPTY_VALUE_PLACEHOLDER}
|
|
500
|
+
subtitle={locale !== "" ? `${slug} · (${locale})` : slug}
|
|
501
|
+
actions={
|
|
502
|
+
canWrite ? (
|
|
503
|
+
<Button type="submit" loading={submitting} disabled={disabled}>
|
|
504
|
+
{submitting
|
|
505
|
+
? t("template-resolver.editor.saving")
|
|
506
|
+
: t("template-resolver.editor.save")}
|
|
507
|
+
</Button>
|
|
508
|
+
) : undefined
|
|
444
509
|
}
|
|
445
510
|
>
|
|
446
|
-
{
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
511
|
+
{loading && <Banner variant="loading">{t("template-resolver.editor.loading")}</Banner>}
|
|
512
|
+
{loadError !== null && (
|
|
513
|
+
<Banner variant="error">
|
|
514
|
+
{t("template-resolver.editor.loadFailed")}: {loadError.code}
|
|
515
|
+
</Banner>
|
|
516
|
+
)}
|
|
517
|
+
{!canWrite && !loading && (
|
|
518
|
+
<Banner variant="info">{t("template-resolver.editor.readOnly")}</Banner>
|
|
519
|
+
)}
|
|
520
|
+
<Field id="text-block-title" label={t("template-resolver.editor.titleLabel")} required>
|
|
521
|
+
<Input
|
|
522
|
+
kind="text"
|
|
523
|
+
id="text-block-title"
|
|
524
|
+
name="text-block-title"
|
|
525
|
+
value={title}
|
|
526
|
+
onChange={setTitle}
|
|
527
|
+
disabled={disabled}
|
|
528
|
+
required
|
|
451
529
|
/>
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
530
|
+
</Field>
|
|
531
|
+
<Field
|
|
532
|
+
id={contentEditorId}
|
|
533
|
+
label={t("template-resolver.editor.contentLabel")}
|
|
534
|
+
labelAppendix={
|
|
535
|
+
<Button
|
|
536
|
+
type="button"
|
|
537
|
+
variant="secondary"
|
|
538
|
+
size="sm"
|
|
539
|
+
onClick={() => setPreviewing((p) => !p)}
|
|
540
|
+
>
|
|
541
|
+
{previewing ? t("kumiko.contentEditor.editMode") : t("kumiko.contentEditor.preview")}
|
|
542
|
+
</Button>
|
|
543
|
+
}
|
|
544
|
+
>
|
|
545
|
+
{previewing ? (
|
|
546
|
+
<ContentPreview
|
|
547
|
+
content={content}
|
|
548
|
+
variables={variableExamples}
|
|
549
|
+
contentFormat={contentFormat}
|
|
460
550
|
/>
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
551
|
+
) : (
|
|
552
|
+
<DocumentBlocksContext.Provider value={documentBlocks}>
|
|
553
|
+
<ContentEditor
|
|
554
|
+
id={contentEditorId}
|
|
555
|
+
value={content}
|
|
556
|
+
onChange={setContent}
|
|
557
|
+
variables={variables}
|
|
558
|
+
readOnly={disabled}
|
|
559
|
+
/>
|
|
560
|
+
</DocumentBlocksContext.Provider>
|
|
561
|
+
)}
|
|
562
|
+
</Field>
|
|
563
|
+
{saveError !== null && <Banner variant="error">{saveError}</Banner>}
|
|
564
|
+
{savedMsg !== null && <Banner variant="info">{savedMsg}</Banner>}
|
|
565
|
+
</Form>
|
|
566
|
+
</>
|
|
467
567
|
);
|
|
468
568
|
}
|
|
469
569
|
|
|
@@ -472,9 +572,10 @@ function TextBlockEditor({
|
|
|
472
572
|
// label/icon/access, the feature only supplies children plus the editor.
|
|
473
573
|
// Without navId: resolver only, no sidebar node (server-only consumers such as
|
|
474
574
|
// money-horse leak nothing).
|
|
475
|
-
// `tenantId
|
|
476
|
-
// SYSTEM_TENANT_ID for apps that seed marketing/legal there.
|
|
477
|
-
// session tenant
|
|
575
|
+
// `tenantId`: which tenant a SystemAdmin's trees and editor serve —
|
|
576
|
+
// SYSTEM_TENANT_ID for apps that seed marketing/legal there. Other users and
|
|
577
|
+
// per-user collections always stay on the session tenant. Omit for the
|
|
578
|
+
// session tenant everywhere.
|
|
478
579
|
//
|
|
479
580
|
// Beyond that single tree, this client also serves every content collection
|
|
480
581
|
// the app declares via r.contentCollection() — one provider per collection,
|
|
@@ -498,7 +599,7 @@ export function textBlocksClient(opts?: {
|
|
|
498
599
|
// this — an app can have both a hand-wired content tree and collections.
|
|
499
600
|
navProvidersFromCollections: (collections) => ({
|
|
500
601
|
providers: Object.fromEntries(
|
|
501
|
-
collections.map((c) => [c.navQn, makeTreeProvider(tenantId, c
|
|
602
|
+
collections.map((c) => [c.navQn, makeTreeProvider(tenantId, c)]),
|
|
502
603
|
),
|
|
503
604
|
entities: Object.fromEntries(collections.map((c) => [c.navQn, ["template-resource"]])),
|
|
504
605
|
}),
|
|
@@ -186,7 +186,6 @@ export const defaultTranslations: TranslationsByLocale = {
|
|
|
186
186
|
"template-resolver.editor.networkError": "Network error while saving.",
|
|
187
187
|
"template-resolver.editor.loading": "Loading current version…",
|
|
188
188
|
"template-resolver.editor.loadFailed": "Could not load block",
|
|
189
|
-
"template-resolver.editor.readOnly":
|
|
190
|
-
"Read-only — TenantAdmin or SystemAdmin role required to make changes.",
|
|
189
|
+
"template-resolver.editor.readOnly": "Read-only. Your role does not allow changes here.",
|
|
191
190
|
},
|
|
192
191
|
};
|
package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts
CHANGED
|
@@ -177,7 +177,8 @@ function isPersonalFileRow(registry: Registry, row: Record<string, unknown>): bo
|
|
|
177
177
|
if (row["entityId"] === null && resolveActionFormFileField(registry, entityType, fieldName)) {
|
|
178
178
|
return true;
|
|
179
179
|
}
|
|
180
|
-
const
|
|
180
|
+
const entity = registry.getEntity(entityType) ?? registry.getReadModelEntity(entityType);
|
|
181
|
+
const fieldDef = entity?.fields[fieldName];
|
|
181
182
|
return isPersonalPiiField(fieldDef);
|
|
182
183
|
}
|
|
183
184
|
|
|
@@ -19,3 +19,7 @@ export const GALLERY_MOR_PRICE_IDS = {
|
|
|
19
19
|
starter: "price_gallery_mor_starter",
|
|
20
20
|
pro: "price_gallery_mor_pro",
|
|
21
21
|
};
|
|
22
|
+
|
|
23
|
+
// Set by the docs gallery config: net catalog prices with a "plus VAT" note and a gross recurring
|
|
24
|
+
// price in the plan-switch preview. The German review gallery runs without it and stays unchanged.
|
|
25
|
+
export const GALLERY_DOCS_VARIANT_ENV = "GALLERY_DOCS_VARIANT";
|
|
@@ -20,6 +20,7 @@ import type { ExtraRouteDefinition } from "@cosmicdrift/kumiko-framework/api";
|
|
|
20
20
|
import type { TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
21
21
|
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
22
|
import {
|
|
23
|
+
GALLERY_DOCS_VARIANT_ENV,
|
|
23
24
|
GALLERY_MOR_PRICE_IDS,
|
|
24
25
|
GALLERY_MOR_PROVIDER,
|
|
25
26
|
GALLERY_OPERATOR_EMAIL,
|
|
@@ -29,6 +30,8 @@ import {
|
|
|
29
30
|
GALLERY_TERMS_SLUG,
|
|
30
31
|
} from "./gallery-constants";
|
|
31
32
|
|
|
33
|
+
const isDocsVariant = process.env[GALLERY_DOCS_VARIANT_ENV] === "1";
|
|
34
|
+
|
|
32
35
|
export const galleryMailTransport = createInMemoryTransport();
|
|
33
36
|
|
|
34
37
|
// The merchant-of-record mock is only offered to tenants the spec opts in, so
|
|
@@ -93,6 +96,7 @@ export function createGalleryBillingFeature(baseUrl: string): FeatureDefinition
|
|
|
93
96
|
tierLabelKey: (tier) => `gallery-screens:plan.${tier}.label`,
|
|
94
97
|
benefits: (tier) => [{ labelKey: `gallery-screens:plan.${tier}.benefit` }],
|
|
95
98
|
resolveCurrentTier,
|
|
99
|
+
...(isDocsVariant && { priceNoteKey: "gallery-screens:plan.price-note" }),
|
|
96
100
|
viewRoles: ["TenantAdmin", "SystemAdmin"],
|
|
97
101
|
successPath: GALLERY_PLAN_PATH,
|
|
98
102
|
cancelPath: GALLERY_PLAN_PATH,
|
|
@@ -115,12 +119,12 @@ export function createGalleryBillingFeature(baseUrl: string): FeatureDefinition
|
|
|
115
119
|
},
|
|
116
120
|
[GALLERY_MOR_PROVIDER]: {
|
|
117
121
|
checkoutNote: {
|
|
118
|
-
de: "Verkäufer ist
|
|
119
|
-
en: "Sold by
|
|
122
|
+
de: "Verkäufer ist Paddle · Rechnung und USt von Paddle",
|
|
123
|
+
en: "Sold by Paddle · invoice and VAT from Paddle",
|
|
120
124
|
},
|
|
121
125
|
vatNote: {
|
|
122
|
-
de: "Rechnung und Umsatzsteuer stellt
|
|
123
|
-
en: "
|
|
126
|
+
de: "Rechnung und Umsatzsteuer stellt Paddle als Merchant of Record.",
|
|
127
|
+
en: "Paddle, as merchant of record, issues the invoice and charges the VAT.",
|
|
124
128
|
},
|
|
125
129
|
},
|
|
126
130
|
},
|
|
@@ -164,7 +168,7 @@ const galleryProviderPlugin: SubscriptionProviderPlugin = {
|
|
|
164
168
|
// consent id fails, so a successful switch in the spec proves the consent went
|
|
165
169
|
// through.
|
|
166
170
|
const galleryMorProviderPlugin: SubscriptionProviderPlugin = {
|
|
167
|
-
displayName: "
|
|
171
|
+
displayName: "Paddle",
|
|
168
172
|
isBillingEnabled: async (ctx) => morEnabledTenants.has(ctx.user.tenantId),
|
|
169
173
|
verifyAndParseWebhook: async (rawBody) => JSON.parse(rawBody),
|
|
170
174
|
priceToTier: {
|
|
@@ -184,6 +188,7 @@ const galleryMorProviderPlugin: SubscriptionProviderPlugin = {
|
|
|
184
188
|
tax: 106,
|
|
185
189
|
currency: "eur",
|
|
186
190
|
credit: 333,
|
|
191
|
+
...(isDocsVariant && { recurringAmount: 2368, recurringTax: 378 }),
|
|
187
192
|
effectiveAt: "2027-01-15T00:00:00Z",
|
|
188
193
|
}),
|
|
189
194
|
applyPlanSwitch: async (_ctx, options) => {
|
|
@@ -218,6 +223,7 @@ export const galleryScreensFeature: FeatureDefinition = defineFeature("gallery-s
|
|
|
218
223
|
r.translations({
|
|
219
224
|
keys: {
|
|
220
225
|
"gallery:nav.billing": { de: "Abrechnung", en: "Billing" },
|
|
226
|
+
"gallery-screens:plan.price-note": { de: "zzgl. USt.", en: "plus VAT" },
|
|
221
227
|
"gallery-screens:plan.free.label": { de: "Free", en: "Free" },
|
|
222
228
|
"gallery-screens:plan.starter.label": { de: "Starter", en: "Starter" },
|
|
223
229
|
"gallery-screens:plan.pro.label": { de: "Pro", en: "Pro" },
|
|
@@ -30,11 +30,11 @@ const port = Number.parseInt(process.env["PORT"] ?? "4196", 10);
|
|
|
30
30
|
|
|
31
31
|
const BILLING_FEATURE = "billing-foundation";
|
|
32
32
|
const EMAIL_FEATURE = "channel-email";
|
|
33
|
-
// The stock app mounts the real Mollie and
|
|
33
|
+
// The stock app mounts the real Mollie and Paddle providers; the gallery's
|
|
34
34
|
// provider choice is the two mocks, which never call out.
|
|
35
35
|
const REAL_PROVIDER_FEATURES: ReadonlySet<string> = new Set([
|
|
36
36
|
"subscription-mollie",
|
|
37
|
-
"subscription-
|
|
37
|
+
"subscription-paddle",
|
|
38
38
|
]);
|
|
39
39
|
|
|
40
40
|
const features = [
|