slotlock 0.7.0 → 0.8.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.
@@ -0,0 +1,78 @@
1
+ // The dashboard Settings pages' styles: a section list beside one column of cards, label-left rows,
2
+ // provider tiles, and the assistant's status card. dashboard.ts renders the pages.
3
+ export const SLOTLOCK_SETTINGS_CSS = `
4
+ .settings { display: grid; grid-template-columns: 176px minmax(0, 640px); gap: var(--space-8); align-items: start; }
5
+ .subnav { display: grid; gap: 2px; position: sticky; top: var(--space-6); }
6
+ .subnav a { display: flex; align-items: center; gap: var(--space-2); height: 32px; padding: 0 var(--space-3); border-radius: var(--radius-sm); color: var(--ink-2); font-size: 14px; text-decoration: none; }
7
+ .subnav a:hover { background: var(--hover); color: var(--ink); }
8
+ .subnav a[aria-current="page"] { background: var(--surface); color: var(--ink); font-weight: 500; box-shadow: inset 0 0 0 1px var(--line); }
9
+ .subnav .nav-dot { width: 6px; height: 6px; border-radius: 50%; margin-left: auto; background: var(--good); }
10
+ .settings-body { display: grid; gap: var(--space-6); min-width: 0; }
11
+ .settings-body > .notice { margin: 0; }
12
+ .card-head { margin: 0 0 calc(var(--space-2) - var(--space-6)); font-size: 13px; font-weight: 600; color: var(--ink-3); }
13
+ .set-card { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; margin: 0; }
14
+ .row { display: grid; grid-template-columns: minmax(120px, 160px) minmax(0, 1fr); gap: var(--space-4); align-items: center; min-height: 56px; padding: 12px 20px; border-top: 1px solid var(--line-soft); }
15
+ .row.stack { grid-template-columns: minmax(0, 1fr); gap: var(--space-3); align-items: start; padding-top: 16px; padding-bottom: 16px; }
16
+ .row:first-child { border-top: 0; }
17
+ .row-label { font-size: 14px; font-weight: 500; color: var(--ink); }
18
+ .row-value { font-size: 14px; color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
19
+ .row-value.muted { color: var(--ink-2); }
20
+ .row input:not([type="radio"]):not([type="checkbox"]), .row select { width: 100%; }
21
+ .mono { font-family: var(--mono); font-size: 13px; }
22
+ .card-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: 12px 20px; border-top: 1px solid var(--line); background: color-mix(in srgb, var(--surface) 70%, var(--canvas)); }
23
+ .card-foot .fine { margin: 0; }
24
+ .card-foot .primary { flex: none; }
25
+ .status-head { display: grid; grid-template-columns: 32px minmax(0, 1fr) auto auto; gap: var(--space-3); align-items: center; padding: 16px 20px; }
26
+ .status-head form { margin: 0; }
27
+ .mono-tile { display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--radius); background: var(--sunken); border: 1px solid var(--line); color: var(--ink); font-weight: 600; font-size: 14px; }
28
+ .status-name { display: grid; gap: 2px; min-width: 0; }
29
+ .status-name strong { font-size: 15px; font-weight: 600; color: var(--ink); }
30
+ .status-name code { color: var(--ink-3); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; background: none; padding: 0; }
31
+ .badge { display: inline-flex; align-items: center; gap: 6px; height: 22px; padding: 0 8px; border-radius: var(--radius-full); font-size: 12px; font-weight: 500; color: var(--ink-2); background: var(--sunken); white-space: nowrap; }
32
+ .badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); }
33
+ .badge.ok { color: var(--good); background: color-mix(in srgb, var(--good) 14%, transparent); }
34
+ .badge.ok::before { background: var(--good); }
35
+ .badge.bad { color: var(--bad); background: color-mix(in srgb, var(--bad) 14%, transparent); }
36
+ .badge.bad::before { background: var(--bad); }
37
+ .status-card .notice { margin: 0; border-radius: 0; border-width: 1px 0 0; box-shadow: none; font-size: 13px; padding: 10px 20px; }
38
+ .status-card .notice.done { color: var(--ink); }
39
+ .tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-2); }
40
+ .tile { position: relative; display: flex; align-items: center; gap: var(--space-2); height: 40px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); font-size: 14px; color: var(--ink); cursor: pointer; transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease); }
41
+ .tile:hover { background: var(--hover); }
42
+ .tile input { position: absolute; opacity: 0; pointer-events: none; }
43
+ .tile .tile-mark { display: grid; place-items: center; flex: none; width: 20px; height: 20px; border-radius: 5px; background: var(--sunken); color: var(--ink-2); font-size: 11px; font-weight: 600; }
44
+ .tile:has(input:checked) { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
45
+ .tile:has(input:checked) .tile-mark { background: var(--accent); color: var(--surface); }
46
+ .tile:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--ring); }
47
+ .provider-form:not(:has(input[name="preset"][value="custom"]:checked)) .custom-only { display: none; }
48
+ .segmented { justify-self: start; display: inline-flex; padding: 2px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--sunken); gap: 2px; }
49
+ .segmented label { position: relative; display: inline-flex; align-items: center; height: 30px; padding: 0 12px; border-radius: 6px; font-size: 13px; color: var(--ink-2); cursor: pointer; }
50
+ .segmented input { position: absolute; opacity: 0; pointer-events: none; }
51
+ .segmented label:has(input:checked) { background: var(--surface); color: var(--ink); box-shadow: 0 0 0 1px var(--line); }
52
+ .field-with-button { display: flex; gap: var(--space-2); align-items: center; }
53
+ .field-with-button input { flex: 1; min-width: 0; }
54
+ .field-with-button button { flex: none; }
55
+ .key-set { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
56
+ .key-set code { background: none; padding: 0; color: var(--ink-2); letter-spacing: 0.04em; }
57
+ .key-set details { display: contents; }
58
+ .key-set details > summary { list-style: none; cursor: pointer; color: var(--accent); font-size: 13px; }
59
+ .key-set details > summary::-webkit-details-marker { display: none; }
60
+ .key-set details[open] > summary { display: none; }
61
+ .key-set details[open] input { flex-basis: 100%; }
62
+ .danger-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: 14px 20px; }
63
+ .danger-row strong { font-size: 14px; font-weight: 500; color: var(--ink); }
64
+ .danger-row details.act > summary { color: var(--bad); }
65
+ @media (max-width: 760px) {
66
+ .settings { grid-template-columns: minmax(0, 1fr); gap: var(--space-4); }
67
+ .subnav { position: static; display: flex; overflow-x: auto; gap: 0; border-bottom: 1px solid var(--line); scrollbar-width: none; }
68
+ .subnav::-webkit-scrollbar { display: none; }
69
+ .subnav a { height: 40px; border-radius: 0; flex: none; }
70
+ .subnav a[aria-current="page"] { background: none; box-shadow: inset 0 -2px 0 var(--accent); }
71
+ .row { grid-template-columns: minmax(0, 1fr); gap: var(--space-2); padding: 12px 16px; }
72
+ .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
73
+ .status-head { grid-template-columns: 32px minmax(0, 1fr) auto; padding: 14px 16px; }
74
+ .status-head form { grid-column: 1 / -1; }
75
+ .card-foot { flex-direction: column; align-items: stretch; padding: 12px 16px; }
76
+ .card-foot .primary { height: 44px; }
77
+ }
78
+ `;
package/dashboard.d.ts CHANGED
@@ -4,7 +4,7 @@ import { type SlotlockAgentCalendarBackend, type SlotlockAgentOperation, type Sl
4
4
  import { type SlotlockAssistantStore } from './assistant.js';
5
5
  import { type SlotlockRate } from './config.js';
6
6
  import { type SlotlockBookingRules } from './booking-rules.js';
7
- import { type SlotlockTenantPlan } from './plans.js';
7
+ import { type SlotlockPlan, type SlotlockTenantPlan } from './plans.js';
8
8
  import { type SlotlockLocation, type SlotlockResourceGroup, type SlotlockResourceProfileInput } from './resource-profile.js';
9
9
  import { type SlotlockUsageRow } from './usage.js';
10
10
  import { type CreateWebhookEndpointResult, type SlotlockChange, type SlotlockWebhookEndpoint } from './event-feed.js';
@@ -233,9 +233,31 @@ export interface SlotlockDashboardAssistantOptions {
233
233
  conversationTtlMs?: number;
234
234
  }
235
235
  /**
236
- * Hosted Slotlock's plans (plans.ts) and their billing: a Plan page with the tenant's plan and use,
236
+ * Hosted Slotlock's plans (plans.ts) and their billing: a Usage page with the tenant's plan and use,
237
237
  * Upgrade (to a checkout page) and Manage (to a billing portal). Off unless set.
238
238
  */
239
+ /**
240
+ * The tenant's plan and the deployment's default, read whether or not anything sells plans: with it
241
+ * the dashboard shows a Usage page (plan, limits and this month's use) and puts a tenant new to
242
+ * plans on the default before it adds what plans limit.
243
+ */
244
+ export interface SlotlockDashboardPlans {
245
+ /** The tenant's plan (its own, or the default once it applies), or null (the server's limits apply). */
246
+ plan(tenantRef: string): Promise<SlotlockTenantPlan | null>;
247
+ /** Puts a tenant new to plans on the default plan before it adds a key, resource or webhook endpoint. */
248
+ enrol?(tenantRef: string): Promise<void>;
249
+ /** The deployment's default plan and the date it applies to every tenant, or null. */
250
+ planDefault?(): Promise<{
251
+ plan: SlotlockPlan;
252
+ limits: SlotlockTenantPlan['limits'];
253
+ appliesFrom: Date;
254
+ } | null>;
255
+ /**
256
+ * Where asking for another plan goes (an https or mailto URL) when nothing sells plans here: with
257
+ * it, the Usage page offers Developer or Team by asking.
258
+ */
259
+ contact?: string;
260
+ }
239
261
  export interface SlotlockDashboardBilling {
240
262
  /** The tenant's plan (its own, or the default), or null when it has none (the server's limits apply). */
241
263
  plan(tenantRef: string): Promise<SlotlockTenantPlan | null>;
@@ -359,15 +381,17 @@ export interface SlotlockDashboardOptions {
359
381
  availability?: readonly WeeklyAvailabilityRule[];
360
382
  /** The chat assistant; off unless set. */
361
383
  assistant?: SlotlockDashboardAssistantOptions;
362
- /** Plans and billing (hosted Slotlock); off unless set. */
384
+ /** Plans (hosted Slotlock): a Usage page, and enrolment on the default plan. Off unless set. */
385
+ plans?: SlotlockDashboardPlans;
386
+ /** Paying for plans through Stripe (hosted Slotlock); off unless set. Implies `plans` from its own. */
363
387
  billing?: SlotlockDashboardBilling;
364
388
  /**
365
- * The deployment meters usage (SLOTLOCK_USAGE=on): the Plan page shows this month's, from the
389
+ * The deployment meters usage (SLOTLOCK_USAGE=on): the Usage page shows this month's, from the
366
390
  * resources adapter's `usage`.
367
391
  */
368
392
  usage?: boolean;
369
393
  /**
370
- * The deployment holds tenants to their plans' monthly quotas (SLOTLOCK_QUOTAS=on): the Plan page
394
+ * The deployment holds tenants to their plans' monthly quotas (SLOTLOCK_QUOTAS=on): the Usage page
371
395
  * shows this month's use against them, and warns from 80%. Needs `usage`.
372
396
  */
373
397
  quotas?: boolean;