@dreamtree-org/landing-kit 0.9.0 → 0.10.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/index.d.ts CHANGED
@@ -57,6 +57,26 @@ export interface AuthShellProps {
57
57
  * the form. Nothing renders when `load` returns no accounts (demo mode off).
58
58
  */
59
59
  demoAccounts?: AuthDemoAccounts | undefined;
60
+ /**
61
+ * Dreamtree ID single sign-on (DTID-32). Absent = today's local form, unchanged.
62
+ * 'hybrid' adds a "Continue with Dreamtree" button and an "or" divider above `children`;
63
+ * 'sso' drops `children` (and the demo-accounts panel) and redirects to the start URL on mount.
64
+ */
65
+ ssoProvider?: SsoProvider | undefined;
66
+ }
67
+
68
+ export interface SsoProvider {
69
+ /** The app's SSO entry point, e.g. '/auth/sso/start'. May already carry a query or #hash. */
70
+ startUrl: string;
71
+ mode: 'hybrid' | 'sso';
72
+ /** Button text. Default "Continue with Dreamtree". */
73
+ label?: string | undefined;
74
+ /** Sent as `dt_company` (the /c/[slug]/login company). */
75
+ companySlug?: string | undefined;
76
+ /** Sent as `return_to`: where the app lands after sign-in. */
77
+ returnTo?: string | undefined;
78
+ /** 'sso' only: redirect with window.location.assign after mount. Default true; the button always stays as a fallback. */
79
+ autoRedirect?: boolean | undefined;
60
80
  }
61
81
 
62
82
  export interface DemoAccount {
@@ -89,6 +109,11 @@ export interface DemoAccountsProps {
89
109
  }
90
110
 
91
111
  export declare function AuthShell(props: AuthShellProps): ReactElement;
112
+ /**
113
+ * `startUrl` plus `return_to` / `dt_company` (URI-encoded, replacing same-named params already
114
+ * there; other params and the #hash are kept). Pure: safe during SSR.
115
+ */
116
+ export declare function buildSsoUrl(startUrl: string, opts?: Pick<SsoProvider, 'returnTo' | 'companySlug'>): string;
92
117
  /** The chip row AuthShell reveals; render it yourself for a login outside AuthShell. */
93
118
  export declare function DemoAccounts(props: DemoAccountsProps): ReactElement | null;
94
119
  /** `taps` clicks, each within `windowMs` of the last, set `revealed` for the page's life. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/landing-kit",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "The shared marketing site, auth frame (AuthShell + brands) and Platform admin console for every Dreamtree product: content-driven landing pages, login/signup shell, platform console client, Express routes, prerender and SEO files.",
5
5
  "author": {
6
6
  "name": "Partha Preetham Krishna",
@@ -106,7 +106,8 @@
106
106
  "esbuild": "^0.25.0",
107
107
  "express": "^4.22.3",
108
108
  "react": "^18.3.1",
109
- "react-dom": "^18.3.1"
109
+ "react-dom": "^18.3.1",
110
+ "react-test-renderer": "^18.3.1"
110
111
  },
111
112
  "engines": {
112
113
  "node": ">=18"
package/platform.d.ts CHANGED
@@ -5,7 +5,7 @@ export const PLATFORM_API_BASE: '/api/platform';
5
5
  export const PLATFORM_LOGIN_PATH: '/platform/login';
6
6
  export const STEP_UP_REQUIRED: 'STEP_UP_REQUIRED';
7
7
 
8
- export type PlatformFeature = 'overview' | 'companies' | 'jobs' | 'plans' | 'audit' | 'support' | 'settings' | 'admins';
8
+ export type PlatformFeature = 'overview' | 'companies' | 'jobs' | 'plans' | 'audit' | 'support' | 'settings' | 'domains' | 'admins';
9
9
  /** Every console feature, in nav order. */
10
10
  export const PLATFORM_FEATURES: readonly PlatformFeature[];
11
11
  /** Features a server without `GET /me` serves. */
@@ -91,7 +91,7 @@ export interface PlatformMe {
91
91
  permissions: string[];
92
92
  /** `console` false = enrollment-only session (enroll an authenticator first). */
93
93
  access?: { console: boolean; stepUpFresh: boolean };
94
- features: Partial<Record<PlatformFeature | 'domains', boolean>>;
94
+ features: Partial<Record<PlatformFeature, boolean>>;
95
95
  app: { key: string; name: string; companyLoginPath?: string };
96
96
  }
97
97
 
@@ -187,28 +187,35 @@ export interface Plan {
187
187
  seats?: number | null;
188
188
  limits?: Record<string, number | null>;
189
189
  features?: string[];
190
+ sort?: number;
190
191
  }
191
192
 
192
193
  export interface Subscription {
193
194
  id: string;
194
195
  companyId: string;
195
196
  planId: string;
197
+ planCode?: string | null;
198
+ planName?: string | null;
196
199
  status: 'trial' | 'active' | 'past_due' | 'cancelled' | 'expired';
197
200
  startsAt: Iso;
198
201
  endsAt: Iso | null;
199
202
  seats?: number | null;
200
203
  notes?: string | null;
204
+ createdBy?: string | null;
205
+ createdAt?: Iso;
201
206
  }
202
207
 
203
208
  export interface SettingsField {
204
209
  key: string;
205
210
  label: string;
206
211
  type: 'text' | 'number' | 'boolean' | 'select' | 'secret' | 'email' | 'url';
207
- options?: string[];
212
+ options?: Array<{ value: string; label: string }>;
208
213
  /** Secret values come back masked (`set: true`), never in clear. */
209
214
  value?: unknown;
210
215
  set?: boolean;
211
216
  help?: string;
217
+ /** Where the value comes from. */
218
+ source?: 'branch' | 'company' | 'default' | null;
212
219
  }
213
220
  export interface SettingsFamily {
214
221
  family: string;
@@ -216,24 +223,37 @@ export interface SettingsFamily {
216
223
  scope: 'platform' | 'company';
217
224
  fields: SettingsField[];
218
225
  }
226
+ /** GET settings?company= */
227
+ export interface SettingsResponse {
228
+ families: SettingsFamily[];
229
+ /** Send back with PUT settings/:family; a stale version answers 409. */
230
+ version: number;
231
+ }
219
232
 
220
233
  export interface SupportGrant {
221
234
  id: string;
222
235
  companyId: string;
223
236
  companyName?: string;
224
237
  requestedBy: string;
238
+ requestedById?: string;
225
239
  approvedBy?: string | null;
226
240
  reason: string;
241
+ /** Length of the access window, counted from approval. */
242
+ minutes?: number | null;
227
243
  status: 'requested' | 'active' | 'expired' | 'revoked';
228
244
  expiresAt: Iso | null;
245
+ approvedAt?: Iso | null;
246
+ revokedAt?: Iso | null;
229
247
  createdAt: Iso;
230
248
  }
231
249
 
232
250
  export interface CompanyDomain {
233
251
  id: string;
234
252
  host: string;
253
+ status?: 'pending' | 'verified';
235
254
  primary: boolean;
236
255
  verifiedAt: Iso | null;
256
+ createdAt?: Iso;
237
257
  verification?: { type: 'TXT'; name: string; value: string };
238
258
  }
239
259
 
@@ -327,6 +347,8 @@ export function usePlatform(): {
327
347
  href(rel?: string): string;
328
348
  navigate(href: string): void;
329
349
  can(permission: string): boolean;
350
+ /** Features this console shows (requested ∩ advertised by GET /me). */
351
+ features: PlatformFeature[];
330
352
  companyLoginPath(slug: string): string;
331
353
  companyLoginUrl(slug: string): string;
332
354
  };
@@ -340,3 +362,15 @@ export const CopyButton: ComponentType<{ value: string; label?: string }>;
340
362
  export const OneTimeSecret: ComponentType<{ title?: string; loginUrl?: string; email: string; password: string; onDone?: () => void }>;
341
363
  export const InspectResult: ComponentType<{ report: Record<string, any> }>;
342
364
  export function describeAction(action: string): string;
365
+ /** "₹999.00 / month" from minor units. */
366
+ export function formatPrice(priceMinor: number, currency?: string, interval?: Plan['interval']): string;
367
+
368
+ /** Built-in v1.1 views, exported for products that compose their own console. They render inside <PlatformConsole>. */
369
+ export const PlansView: ComponentType;
370
+ export const SupportAccessView: ComponentType;
371
+ export const SupportGrants: ComponentType<{ companyId?: string; companyName?: string }>;
372
+ type CompanyTabProps = { company: { id: string; displayName?: string } & Record<string, any>; setFlash(flash: { kind: 'success' | 'error' | 'warning' | 'info'; msg: string } | null): void };
373
+ export const SubscriptionTab: ComponentType<CompanyTabProps>;
374
+ export const DomainsTab: ComponentType<CompanyTabProps>;
375
+ export const SettingsTab: ComponentType<CompanyTabProps>;
376
+ export const SupportTab: ComponentType<CompanyTabProps>;
package/styles.css CHANGED
@@ -850,6 +850,63 @@ html:has(.dt-landing) { scroll-behavior: smooth; scroll-padding-top: calc(var(--
850
850
  .dt-auth-demo-note { margin: 0.625rem 0 0; font-size: 0.75rem; color: var(--dt-auth-muted); }
851
851
  .dt-auth-demo-note code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.75rem; color: inherit; }
852
852
 
853
+ /* ---- Dreamtree ID single sign-on (ssoProvider, DTID-32) ---- */
854
+ .dt-auth-sso-btn {
855
+ display: flex;
856
+ align-items: center;
857
+ justify-content: center;
858
+ gap: 0.5rem;
859
+ width: 100%;
860
+ min-height: 2.75rem;
861
+ padding: 0.625rem 1rem;
862
+ border: 1px solid var(--dt-brand);
863
+ border-radius: 0.5rem;
864
+ background: var(--dt-brand);
865
+ color: #fff;
866
+ font: inherit;
867
+ font-size: 0.9375rem;
868
+ font-weight: 600;
869
+ text-decoration: none;
870
+ transition: background-color 120ms;
871
+ }
872
+ .dt-auth-sso-btn:hover { background: color-mix(in oklab, var(--dt-brand) 88%, black); }
873
+ .dt-auth-sso-btn:focus-visible { outline: 2px solid var(--dt-brand); outline-offset: 2px; }
874
+ .dt-auth-sso-icon { flex: none; }
875
+ .dt-auth-or {
876
+ display: flex;
877
+ align-items: center;
878
+ gap: 0.75rem;
879
+ margin: 1.25rem 0;
880
+ font-size: 0.75rem;
881
+ letter-spacing: 0.08em;
882
+ text-transform: uppercase;
883
+ color: var(--dt-auth-muted);
884
+ }
885
+ .dt-auth-or::before,
886
+ .dt-auth-or::after { content: ''; flex: 1; border-top: 1px solid var(--dt-auth-border); }
887
+ .dt-auth-sso-status {
888
+ display: flex;
889
+ align-items: center;
890
+ gap: 0.625rem;
891
+ margin: 0 0 1rem;
892
+ font-size: 0.875rem;
893
+ color: var(--dt-auth-muted);
894
+ }
895
+ .dt-auth-sso-spinner {
896
+ flex: none;
897
+ width: 1rem;
898
+ height: 1rem;
899
+ border: 2px solid var(--dt-brand-soft);
900
+ border-top-color: var(--dt-brand);
901
+ border-radius: 999px;
902
+ animation: dt-auth-sso-spin 0.8s linear infinite;
903
+ }
904
+ @keyframes dt-auth-sso-spin { to { transform: rotate(360deg); } }
905
+ @media (prefers-reduced-motion: reduce) {
906
+ .dt-auth-sso-spinner { animation: none; }
907
+ .dt-auth-sso-btn { transition: none; }
908
+ }
909
+
853
910
  /* ---- phones ---- */
854
911
  .dt-auth-column,
855
912
  .dt-auth-card { min-width: 0; }