@dreamtree-org/landing-kit 0.8.0 → 0.10.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/MIGRATION.md +30 -0
- package/PLATFORM-API.md +17 -13
- package/README.md +27 -1
- package/ai-skills/landing-kit.md +7 -6
- package/dist/index.cjs +323 -15
- package/dist/index.js +323 -15
- package/dist/platform.cjs +1094 -398
- package/dist/platform.js +1092 -396
- package/dist/schema.cjs +324 -3
- package/dist/schema.js +304 -3
- package/index.d.ts +15 -1
- package/package.json +1 -1
- package/platform.d.ts +37 -3
- package/schema.d.ts +50 -0
package/MIGRATION.md
CHANGED
|
@@ -159,3 +159,33 @@ Nothing changes for products that don't use it. The server half, `createPlatform
|
|
|
159
159
|
- Credential-custodian "Reveal URL" with a reason, audited.
|
|
160
160
|
|
|
161
161
|
**Not in the console yet:** plans and subscriptions, platform settings, support access and domains. Their server endpoints come first (tenancy). The nav shows them only when `GET /me` advertises them.
|
|
162
|
+
|
|
163
|
+
## 0.9.0 — store documents (additive)
|
|
164
|
+
|
|
165
|
+
A store's landing, listing and product pages, its header and footer, and its product card are now one
|
|
166
|
+
version-2 document (`validateStore` from `/schema`). Nothing changes for apps that keep version-1 tenant pages.
|
|
167
|
+
|
|
168
|
+
- To adopt it, run each stored v1 page through `migrateStore(page)`. It becomes `pages.home`, and the listing
|
|
169
|
+
and product pages get their default templates. `validateStore` migrates a v1 page itself, so an app can
|
|
170
|
+
switch over on its next save.
|
|
171
|
+
- Listing and product pages are templates whose blocks are either app **slots** (`filters`, `results`,
|
|
172
|
+
`gallery`, `buy_box`…, rendered by the app through `TemplateRegion`'s `slots`) or ordinary content sections.
|
|
173
|
+
Required slots cannot be hidden or removed.
|
|
174
|
+
- `safeHref` now rejects links containing a backslash, and registry lookups (`blockDef`, `templatePage`) ignore
|
|
175
|
+
inherited keys, so `__proto__` or `constructor` is an unknown type.
|
|
176
|
+
|
|
177
|
+
## 0.10.0 — Platform console: plans, settings, support access, domains (additive)
|
|
178
|
+
|
|
179
|
+
The console now shows the v1.1 features that `@dreamtree-org/tenancy` 0.5.0 serves. Nothing appears until the
|
|
180
|
+
server advertises it in `GET /me`, so a product on tenancy 0.4.0 sees no change.
|
|
181
|
+
|
|
182
|
+
- **New pages:** Plans (`/platform/plans`, `plans.read`) and Support access (`/platform/support-access`,
|
|
183
|
+
`support.request`).
|
|
184
|
+
- **New company tabs**, between Admins and the product's `companyTabs`: Subscription, Domains, Settings and
|
|
185
|
+
Support. Each needs its feature and read permission.
|
|
186
|
+
- `'domains'` joins `PLATFORM_FEATURES` and the `PlatformFeature` type. `usePlatform()` gains `features`.
|
|
187
|
+
- New exports: `formatPrice`, `PlansView`, `SupportAccessView`, `SupportGrants`, `SubscriptionTab`,
|
|
188
|
+
`DomainsTab`, `SettingsTab`, `SupportTab`.
|
|
189
|
+
- A product that already has its own settings or subscription tab in `companyTabs` should drop it once the
|
|
190
|
+
server serves the built-in one, or the company page shows both.
|
|
191
|
+
|
package/PLATFORM-API.md
CHANGED
|
@@ -5,7 +5,7 @@ server only through this contract. The server side is `createPlatformApi` in `@d
|
|
|
5
5
|
mounted with `./next` (`app/api/platform/[...path]/route.ts`) or `./express` (`app.use('/api/platform', …)`).
|
|
6
6
|
A product that implements these endpoints itself gets the same console.
|
|
7
7
|
|
|
8
|
-
**Server status.** `@dreamtree-org/tenancy` 0.
|
|
8
|
+
**Server status.** `@dreamtree-org/tenancy` 0.5.0 implements the whole contract: everything marked shipped, and every v1.1 item (TOTP, throttle, rotation, `/me`, roles, audit, plans, settings, support access and domains). The console in landing-kit 0.10.0 shows all of them.
|
|
9
9
|
|
|
10
10
|
The contract was taken from procurementpro's `app/api/platform/**`, the reference console. Paths are
|
|
11
11
|
relative to the base path, `/api/platform` by default (`createPlatformClient({ basePath })`).
|
|
@@ -63,14 +63,16 @@ relative to the base path, `/api/platform` by default (`createPlatformClient({ b
|
|
|
63
63
|
```json
|
|
64
64
|
{
|
|
65
65
|
"admin": { "id": "…", "email": "…", "roles": ["superadmin"], "totpEnrolled": true },
|
|
66
|
-
"permissions": ["companies.read", "companies.write", "admins.write", "jobs.write", "db.reveal", "plans.write", "settings.write", "support.grant"],
|
|
66
|
+
"permissions": ["companies.read", "companies.write", "admins.write", "jobs.write", "db.reveal", "plans.write", "settings.write", "support.grant", "domains.write"],
|
|
67
67
|
"access": { "console": true, "stepUpFresh": false },
|
|
68
68
|
"features": { "jobs": true, "plans": true, "settings": true, "support": true, "domains": true, "audit": true },
|
|
69
69
|
"app": { "key": "procurementpro", "name": "ProcurementPro", "companyLoginPath": "/c/:slug/login" }
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
**Roles.** `superadmin` holds every permission. `support` holds `companies.read`, `admins.read`, `jobs.read`, `audit.read`, `plans.read` and `support.request`.
|
|
73
|
+
**Roles.** `superadmin` holds every permission. `support` holds `companies.read`, `admins.read`, `jobs.read`, `audit.read`, `plans.read`, `domains.read` and `support.request`.
|
|
74
|
+
|
|
75
|
+
**Permissions per v1.1 feature:** plans and subscriptions use `plans.read` / `plans.write`; settings use `settings.read` / `settings.write`; support access uses `support.request` (list and request) and `support.grant` (approve and revoke); domains use `domains.read` / `domains.write`.
|
|
74
76
|
|
|
75
77
|
Until `/me` exists, the console falls back to the shipped feature set (overview, companies, admins, and
|
|
76
78
|
jobs when `GET /jobs` answers `enabled: true`).
|
|
@@ -157,14 +159,16 @@ advertised.
|
|
|
157
159
|
| POST | `/plans` | `Plan` without `id` | **step-up** | v1.1 |
|
|
158
160
|
| PATCH | `/plans/:id` | any `Plan` field | Archive with `{ status: "archived" }`; plans are never deleted. **step-up** | v1.1 |
|
|
159
161
|
| GET | `/companies/:id/subscription` | — | `{ subscription: Subscription \| null, history: Subscription[] }` | v1.1 |
|
|
160
|
-
| PUT | `/companies/:id/subscription` | `{ planId, status, startsAt?, endsAt?, seats?, notes? }` | **step-up** | v1.1 |
|
|
162
|
+
| PUT | `/companies/:id/subscription` | `{ planId, status, startsAt?, endsAt?, seats?, notes? }` | `{ subscription, history }`. Appends a row; an archived plan is refused unless the company is already on it. **step-up** | v1.1 |
|
|
161
163
|
|
|
162
|
-
###
|
|
164
|
+
### Company settings
|
|
163
165
|
|
|
164
166
|
| Method | Path | Body | Response / notes | Status |
|
|
165
167
|
|---|---|---|---|---|
|
|
166
|
-
| GET | `/settings` | `?company=` (
|
|
167
|
-
| PUT | `/settings/:family` | `{ companyId
|
|
168
|
+
| GET | `/settings` | `?company=` (required) | `{ families: SettingsFamily[], version }`. Secret fields carry `set`, never a value | v1.1 |
|
|
169
|
+
| PUT | `/settings/:family` | `{ companyId, values, version }` | `{ ok, changed, families, version }`. A blank secret keeps the stored one; a stale `version` answers 409 `CONFLICT`. The audit records the keys changed, never the values. **step-up** | v1.1 |
|
|
170
|
+
|
|
171
|
+
Settings are per company. Platform-wide settings are not part of the contract yet.
|
|
168
172
|
|
|
169
173
|
### Support access
|
|
170
174
|
|
|
@@ -174,8 +178,8 @@ data (knowledge doc §5.8).
|
|
|
174
178
|
| Method | Path | Body | Response / notes | Status |
|
|
175
179
|
|---|---|---|---|---|
|
|
176
180
|
| GET | `/support-access` | `?company&status` | `{ grants: SupportGrant[] }` | v1.1 |
|
|
177
|
-
| POST | `/support-access` | `{ companyId, reason, minutes }` |
|
|
178
|
-
| POST | `/support-access/:id` | `{ action: "approve" \| "revoke" }` | **step-up** | v1.1 |
|
|
181
|
+
| POST | `/support-access` | `{ companyId, reason, minutes }` | `{ grant }`. Reason 8–255 characters; minutes 15–480. An unapproved request lapses after a day | v1.1 |
|
|
182
|
+
| POST | `/support-access/:id` | `{ action: "approve" \| "revoke" }` | `{ grant }`. The requester cannot approve their own grant; the window starts at approval. **step-up** | v1.1 |
|
|
179
183
|
|
|
180
184
|
### Domains
|
|
181
185
|
|
|
@@ -183,7 +187,7 @@ data (knowledge doc §5.8).
|
|
|
183
187
|
|---|---|---|---|---|
|
|
184
188
|
| GET | `/companies/:id/domains` | — | `{ domains: CompanyDomain[] }` | v1.1 |
|
|
185
189
|
| POST | `/companies/:id/domains` | `{ host }` | `{ domain, verification: { type: "TXT", name, value } }` | v1.1 |
|
|
186
|
-
| POST | `/companies/:id/domains/:domainId` | `{ action: "verify" \| "make-primary" \| "remove" }` | `remove` needs **step-up** | v1.1 |
|
|
190
|
+
| POST | `/companies/:id/domains/:domainId` | `{ action: "verify" \| "make-primary" \| "remove" }` | `verify` answers `{ ok, verified, domain }` (`verified: false` until the TXT record is published). The first verified domain becomes primary. `remove` needs **step-up** | v1.1 |
|
|
187
191
|
|
|
188
192
|
## Shapes
|
|
189
193
|
|
|
@@ -206,8 +210,8 @@ The server writes one `platform_audit_events` row per mutating call. Action name
|
|
|
206
210
|
- `company.admin.create`, `branch.create`
|
|
207
211
|
- `database.test`, `database.replace`, `database.credentials.reveal`
|
|
208
212
|
- `job.run`, `job.retry`, `schedule.create`
|
|
209
|
-
- `plan.update`, `subscription.update`, `settings.update`
|
|
210
|
-
- `support.grant.request`, `support.grant.approve`
|
|
211
|
-
- `domain.add`
|
|
213
|
+
- `plan.create`, `plan.update`, `subscription.update`, `settings.update`
|
|
214
|
+
- `support.grant.request`, `support.grant.approve`, `support.grant.revoke`
|
|
215
|
+
- `domain.add`, `domain.verify`, `domain.primary`, `domain.remove`
|
|
212
216
|
|
|
213
217
|
Products may add their own actions under their app key.
|
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ Every star ⭐, share, and contribution helps more than you know. Thank you for
|
|
|
54
54
|
- `<LandingPage brand={brand} content={content} />` — a product's landing page is a content file.
|
|
55
55
|
- `<AuthShell brand={brand} title="Welcome back">…</AuthShell>` (also available at `@dreamtree-org/landing-kit/auth` + `@dreamtree-org/landing-kit/auth.css`).
|
|
56
56
|
- The built-in `brands` / `getBrand(id)` registry is **deprecated** (warns once, removed in 0.5.0).
|
|
57
|
-
- **Platform admin console** (`@dreamtree-org/landing-kit/platform`, 0.8.0+): one HTTP contract ([PLATFORM-API.md](./PLATFORM-API.md)) that every product's super-administrator console speaks, and its browser client (`createPlatformClient`, `createStepUp`). The server half is `createPlatformApi` in `@dreamtree-org/tenancy`. The shared React shell and views (overview, companies, jobs, admins, audit
|
|
57
|
+
- **Platform admin console** (`@dreamtree-org/landing-kit/platform`, 0.8.0+): one HTTP contract ([PLATFORM-API.md](./PLATFORM-API.md)) that every product's super-administrator console speaks, and its browser client (`createPlatformClient`, `createStepUp`). The server half is `createPlatformApi` in `@dreamtree-org/tenancy`. The shared React shell and views (overview, companies, jobs, admins, audit; and from 0.10.0 plans, subscriptions, company settings, support access and domains) build on this client.
|
|
58
58
|
- Plain CSS (`--dt-*` custom properties). It looks the same under Tailwind 3, Tailwind 4 (preflight or not) or no Tailwind. Dark mode via `[data-theme="dark"]` or `.dark`.
|
|
59
59
|
- Built for prerendered, **un-hydrated** pages (fast and crawlable): `enhanceLanding()` wires the menu and the demo button in a few hundred bytes. It also works hydrated in an SPA or Next.js (`'use client'`, `onBookDemo`, `linkComponent`).
|
|
60
60
|
- Responsive from 320px up. The nav folds into a menu below 1024px. Checked with Playwright at 320/375/768/1024/1440, light and dark.
|
|
@@ -202,6 +202,32 @@ import '@dreamtree-org/landing-kit/styles.css';
|
|
|
202
202
|
- `newSection(type)` / `sectionDefaults(type)` start a section with valid defaults; `defaultPage({ name, tagline })`
|
|
203
203
|
is a neutral starting page.
|
|
204
204
|
|
|
205
|
+
### Store documents (0.9.0): landing, listing and product pages as one document
|
|
206
|
+
|
|
207
|
+
A store's whole storefront design is one version-2 document, validated with `validateStore` from `/schema`:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
{ version: 2,
|
|
211
|
+
pages: { home: PageDoc /* the landing page above */,
|
|
212
|
+
listing: { template: 'classic' | 'topbar' | 'editorial', settings, blocks },
|
|
213
|
+
product: { template: 'classic' | 'immersive' | 'compact', settings, blocks } },
|
|
214
|
+
chrome: { announcement, header, footer }, // CHROME_FIELDS
|
|
215
|
+
card: { image_ratio, badges, spec_line, compare, quick_add, mrp, hover } } // CARD_FIELDS
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- `STORE_PAGES[page]` lists each page's `settings` (field defs, so a builder generates the form), `templates`
|
|
219
|
+
(setting values + default block order) and `slots`: the commerce blocks the app renders (results, filters,
|
|
220
|
+
gallery, buy box…). `required` slots can be moved, never removed or hidden, so a page always sells.
|
|
221
|
+
- Between the slots a page may place content sections from `CONTENT_BLOCKS` (promo banner, FAQ, USP strip…),
|
|
222
|
+
validated with the same rules as landing-page sections. At most `MAX_BLOCKS` (12) blocks per page.
|
|
223
|
+
- A version-1 landing page passed to `validateStore` is migrated (`migrateStore`): it becomes `pages.home`
|
|
224
|
+
and everything else takes its defaults. `isLegacyPage(doc)` tells them apart.
|
|
225
|
+
- Render a template page with `<TemplateRegion blocks slots theme resolveMedia />`: `slots[type]` is the app's
|
|
226
|
+
component for a commerce block (`{ block, props }`; return null to place it yourself), content sections render
|
|
227
|
+
like `TenantPage`.
|
|
228
|
+
- Helpers: `templatePage(page, template)`, `newBlock(page, type)`, `blockDef(page, type)`, `defaultStore`,
|
|
229
|
+
`defaultChrome`, `defaultCard`, `storeMediaRefs(doc)` (every `{ media_id }` to check before storing), `validateFields`.
|
|
230
|
+
|
|
205
231
|
### `<AuthShell>` & `@dreamtree-org/landing-kit/auth`
|
|
206
232
|
|
|
207
233
|
The shared auth frame for login, signup, password reset, consent and platform-admin sign-in (`desktop [ brand panel 40% | form card ]`, `mobile [ logo · form card · footer · legal links ]`). Import from `@dreamtree-org/landing-kit` or the lightweight `@dreamtree-org/landing-kit/auth` subpath (with `@dreamtree-org/landing-kit/auth.css` or `styles.css`).
|
package/ai-skills/landing-kit.md
CHANGED
|
@@ -59,7 +59,7 @@ const { password } = await sensitive(() => api.post(`admins/${id}`, { action: 'r
|
|
|
59
59
|
- **Cancelled step-up.** A step-up the admin cancels rejects with `code: 'CANCELLED'`. Treat it as a no-op, not an error toast.
|
|
60
60
|
- **Enrollment gate.** A 403 `TOTP_ENROLLMENT_REQUIRED`, or `me.access.console === false`, means show the authenticator enrolment screen (`POST me/totp` with `begin`, then `verify`), not an error.
|
|
61
61
|
- **One-time secrets.** New passwords and revealed DB URLs come back exactly once. Show them in a copy-once panel, and never log them or put them in state that persists.
|
|
62
|
-
- **Feature gating.** Call `GET me` (v1.1 servers) and pass the result to `enabledFeatures(PLATFORM_FEATURES, me)`. That decides which nav items to show: jobs, plans, audit, support, settings. Older servers fall back to overview, companies, jobs and admins.
|
|
62
|
+
- **Feature gating.** Call `GET me` (v1.1 servers) and pass the result to `enabledFeatures(PLATFORM_FEATURES, me)`. That decides which nav items and company tabs to show: jobs, plans, audit, support, settings, domains. Older servers fall back to overview, companies, jobs and admins.
|
|
63
63
|
|
|
64
64
|
## React console
|
|
65
65
|
|
|
@@ -76,13 +76,14 @@ const { password } = await sensitive(() => api.post(`admins/${id}`, { action: 'r
|
|
|
76
76
|
- Companies: list, 4-step new-company wizard, detail with overview, branches, database (test, sync, replace, reveal), admins, jobs and activity
|
|
77
77
|
- Background jobs: runs, schedules, workers and queues
|
|
78
78
|
- Platform admins
|
|
79
|
-
- **v1.1
|
|
79
|
+
- **v1.1** (served by tenancy 0.5.0, shown by landing-kit 0.10.0):
|
|
80
80
|
- TOTP sign-in and enrolment, login throttle, `GET /me` with a role-based permission list
|
|
81
81
|
- Global audit viewer and CSV export
|
|
82
|
-
- Plans and
|
|
83
|
-
-
|
|
84
|
-
- Support
|
|
85
|
-
-
|
|
82
|
+
- Plans page, and a Subscription tab on each company
|
|
83
|
+
- Settings tab on each company (secret fields are write-only; saves carry `version`)
|
|
84
|
+
- Support access page, and a Support tab on each company (a second admin approves)
|
|
85
|
+
- Domains tab on each company (TXT verification, primary, remove)
|
|
86
|
+
- **Built in, not product tabs.** Subscription, Domains, Settings and Support are console built-ins that appear when `GET /me` advertises the feature. Don't re-implement them in `companyTabs`.
|
|
86
87
|
|
|
87
88
|
## Security invariants (do not weaken)
|
|
88
89
|
|