@cosmoops/connect-sdk 0.9.0 → 0.15.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/CHANGELOG.md +497 -0
- package/README.md +111 -41
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +20 -1
- package/dist/cjs/client/components/app-shell.js +75 -41
- package/dist/cjs/client/components/apps-grid.js +3 -2
- package/dist/cjs/client/components/ask-ai.js +20 -1
- package/dist/cjs/client/components/confirm-dialog.js +40 -0
- package/dist/cjs/client/components/cursor-pagination.js +22 -0
- package/dist/cjs/client/components/filter-tabs.js +24 -0
- package/dist/cjs/client/components/grievance-notice.js +30 -0
- package/dist/cjs/client/components/link-button.js +17 -10
- package/dist/cjs/client/components/logo.js +4 -3
- package/dist/cjs/client/components/nav.js +41 -0
- package/dist/cjs/client/components/plan-pricing.js +27 -0
- package/dist/cjs/client/components/plans.js +1 -1
- package/dist/cjs/client/components/platform-gate.js +48 -0
- package/dist/cjs/client/components/shell-context.js +149 -0
- package/dist/cjs/client/components/side-nav.js +49 -23
- package/dist/cjs/client/components/sidebar.js +127 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/user-menu.js +96 -37
- package/dist/cjs/client/components/workspace-switcher.js +100 -0
- package/dist/cjs/client/index.js +69 -2
- package/dist/cjs/client/provider.js +57 -2
- package/dist/cjs/client/use-checkout.js +9 -5
- package/dist/cjs/errors.js +1 -1
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +25 -2
- package/dist/cjs/server/handler.js +196 -18
- package/dist/cjs/server/http.js +6 -2
- package/dist/cjs/server/resources/acting-as.js +16 -0
- package/dist/cjs/server/resources/ai.js +15 -2
- package/dist/cjs/server/resources/data.js +374 -0
- package/dist/cjs/server/resources/identity.js +7 -3
- package/dist/cjs/server/resources/kv.js +84 -0
- package/dist/cjs/server/resources/platform.js +43 -0
- package/dist/cjs/server/resources/sessions.js +1 -1
- package/dist/cjs/server/resources/workspaces.js +74 -0
- package/dist/cjs/store.js +152 -0
- package/dist/cjs/ui/empty-state.js +28 -0
- package/dist/cjs/ui/index.js +44 -0
- package/dist/cjs/ui/page-header.js +27 -0
- package/dist/cjs/ui/page-shell.js +44 -0
- package/dist/cjs/ui/section.js +20 -0
- package/dist/cjs/ui/setting-row.js +14 -0
- package/dist/cjs/ui/status-chip.js +17 -0
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +20 -1
- package/dist/esm/client/components/app-shell.js +81 -47
- package/dist/esm/client/components/apps-grid.js +3 -2
- package/dist/esm/client/components/ask-ai.js +20 -1
- package/dist/esm/client/components/confirm-dialog.js +37 -0
- package/dist/esm/client/components/cursor-pagination.js +19 -0
- package/dist/esm/client/components/filter-tabs.js +21 -0
- package/dist/esm/client/components/grievance-notice.js +27 -0
- package/dist/esm/client/components/link-button.js +17 -10
- package/dist/esm/client/components/logo.js +4 -3
- package/dist/esm/client/components/nav.js +40 -0
- package/dist/esm/client/components/plan-pricing.js +25 -0
- package/dist/esm/client/components/plans.js +2 -2
- package/dist/esm/client/components/platform-gate.js +45 -0
- package/dist/esm/client/components/shell-context.js +141 -0
- package/dist/esm/client/components/side-nav.js +48 -21
- package/dist/esm/client/components/sidebar.js +120 -0
- package/dist/esm/client/components/subscribe-dialog.js +3 -3
- package/dist/esm/client/components/subscription.js +2 -2
- package/dist/esm/client/components/user-menu.js +95 -38
- package/dist/esm/client/components/workspace-switcher.js +97 -0
- package/dist/esm/client/index.js +46 -4
- package/dist/esm/client/provider.js +53 -2
- package/dist/esm/client/use-checkout.js +9 -5
- package/dist/esm/errors.js +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +25 -2
- package/dist/esm/server/handler.js +196 -18
- package/dist/esm/server/http.js +6 -2
- package/dist/esm/server/resources/acting-as.js +13 -0
- package/dist/esm/server/resources/ai.js +15 -2
- package/dist/esm/server/resources/data.js +369 -0
- package/dist/esm/server/resources/identity.js +7 -3
- package/dist/esm/server/resources/kv.js +80 -0
- package/dist/esm/server/resources/platform.js +39 -0
- package/dist/esm/server/resources/sessions.js +1 -1
- package/dist/esm/server/resources/workspaces.js +70 -0
- package/dist/esm/store.js +141 -0
- package/dist/esm/ui/empty-state.js +25 -0
- package/dist/esm/ui/index.js +34 -0
- package/dist/esm/ui/page-header.js +24 -0
- package/dist/esm/ui/page-shell.js +41 -0
- package/dist/esm/ui/section.js +17 -0
- package/dist/esm/ui/setting-row.js +11 -0
- package/dist/esm/ui/status-chip.js +14 -0
- package/dist/types/client/components/app-shell.d.ts +65 -27
- package/dist/types/client/components/apps-grid.d.ts +11 -2
- package/dist/types/client/components/confirm-dialog.d.ts +34 -0
- package/dist/types/client/components/cursor-pagination.d.ts +27 -0
- package/dist/types/client/components/filter-tabs.d.ts +26 -0
- package/dist/types/client/components/grievance-notice.d.ts +31 -0
- package/dist/types/client/components/link-button.d.ts +32 -9
- package/dist/types/client/components/logo.d.ts +18 -2
- package/dist/types/client/components/nav.d.ts +24 -0
- package/dist/types/client/components/plan-pricing.d.ts +10 -0
- package/dist/types/client/components/platform-gate.d.ts +53 -0
- package/dist/types/client/components/shell-context.d.ts +116 -0
- package/dist/types/client/components/side-nav.d.ts +27 -13
- package/dist/types/client/components/sidebar.d.ts +95 -0
- package/dist/types/client/components/user-menu.d.ts +68 -22
- package/dist/types/client/components/workspace-switcher.d.ts +49 -0
- package/dist/types/client/index.d.ts +27 -5
- package/dist/types/client/provider.d.ts +109 -6
- package/dist/types/client/use-checkout.d.ts +8 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +25 -2
- package/dist/types/server/http.d.ts +8 -2
- package/dist/types/server/index.d.ts +4 -0
- package/dist/types/server/resources/acting-as.d.ts +11 -0
- package/dist/types/server/resources/ai.d.ts +9 -1
- package/dist/types/server/resources/data.d.ts +258 -0
- package/dist/types/server/resources/kv.d.ts +63 -0
- package/dist/types/server/resources/platform.d.ts +32 -0
- package/dist/types/server/resources/workspaces.d.ts +55 -0
- package/dist/types/store.d.ts +96 -0
- package/dist/types/types.d.ts +319 -15
- package/dist/types/ui/empty-state.d.ts +34 -0
- package/dist/types/ui/index.d.ts +40 -0
- package/dist/types/ui/page-header.d.ts +34 -0
- package/dist/types/ui/page-shell.d.ts +39 -0
- package/dist/types/ui/section.d.ts +26 -0
- package/dist/types/ui/setting-row.d.ts +18 -0
- package/dist/types/ui/status-chip.d.ts +36 -0
- package/package.json +6 -2
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.increment = increment;
|
|
4
|
+
exports.serverTimestamp = serverTimestamp;
|
|
5
|
+
exports.arrayUnion = arrayUnion;
|
|
6
|
+
exports.arrayRemove = arrayRemove;
|
|
7
|
+
exports.deleteField = deleteField;
|
|
8
|
+
exports.unchangedSince = unchangedSince;
|
|
9
|
+
exports.documentExists = documentExists;
|
|
10
|
+
exports.storeIndexFor = storeIndexFor;
|
|
11
|
+
exports.storeIndexSignature = storeIndexSignature;
|
|
12
|
+
/**
|
|
13
|
+
* Building blocks for `data.commit` - the transform sentinels, and the two
|
|
14
|
+
* ways to say "only if nothing has moved".
|
|
15
|
+
*
|
|
16
|
+
* Isomorphic and dependency-free, so a Server Component and a client component
|
|
17
|
+
* building the same write use the same constructors. They are functions rather
|
|
18
|
+
* than object literals you write by hand because the sentinel shape is an
|
|
19
|
+
* encoding, not an API: `{ __transform: "increment", by: 1 }` is what goes over
|
|
20
|
+
* the wire, and nothing outside this file should have to know that.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Add to a number, atomically.
|
|
24
|
+
*
|
|
25
|
+
* **The one operation you genuinely cannot do yourself.** Increments are
|
|
26
|
+
* commutative, so two concurrent `increment(500)` calls both land - where
|
|
27
|
+
* read-add-write from your own process loses one of them however carefully it
|
|
28
|
+
* retries, because the read and the write are two round trips with a gap in
|
|
29
|
+
* between. Use this for balances, counters and tallies, and reach for a
|
|
30
|
+
* precondition only when the new value depends on the old one in a way
|
|
31
|
+
* addition cannot express.
|
|
32
|
+
*
|
|
33
|
+
* Negative to subtract; there is no separate `decrement`.
|
|
34
|
+
*/
|
|
35
|
+
function increment(by) {
|
|
36
|
+
return { __transform: "increment", by };
|
|
37
|
+
}
|
|
38
|
+
/** Connect's own write time, rather than a clock reading from your process. */
|
|
39
|
+
function serverTimestamp() {
|
|
40
|
+
return { __transform: "serverTimestamp" };
|
|
41
|
+
}
|
|
42
|
+
/** Adds the values not already in the array. Order is not preserved. */
|
|
43
|
+
function arrayUnion(...values) {
|
|
44
|
+
return { __transform: "arrayUnion", values };
|
|
45
|
+
}
|
|
46
|
+
/** Removes every occurrence of each value. */
|
|
47
|
+
function arrayRemove(...values) {
|
|
48
|
+
return { __transform: "arrayRemove", values };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Removes the field entirely.
|
|
52
|
+
*
|
|
53
|
+
* `merge` only - a `replace` already drops every field it does not list, so
|
|
54
|
+
* asking it to delete one is a contradiction rather than a narrowing.
|
|
55
|
+
*/
|
|
56
|
+
function deleteField() {
|
|
57
|
+
return { __transform: "delete" };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* "Only if this document has not changed since I read it."
|
|
61
|
+
*
|
|
62
|
+
* Pass the document a read returned, and the precondition is built from its
|
|
63
|
+
* `_updateTime`. Taking the document rather than the timestamp is deliberate:
|
|
64
|
+
* a caller who has to reach for `doc.data._updateTime` themselves is one typo
|
|
65
|
+
* away from a precondition that silently does nothing.
|
|
66
|
+
*
|
|
67
|
+
* Throws rather than degrading when the document carries no `_updateTime` -
|
|
68
|
+
* that only happens for a document you built yourself rather than read, and a
|
|
69
|
+
* precondition that quietly became "no precondition" is worse than a crash at
|
|
70
|
+
* the call site.
|
|
71
|
+
*/
|
|
72
|
+
function unchangedSince(document) {
|
|
73
|
+
const updateTime = document.data._updateTime;
|
|
74
|
+
if (typeof updateTime !== "string") {
|
|
75
|
+
throw new Error(`Document "${document.id}" has no _updateTime, so there is no version to hold it to. Preconditions are built from documents you read back, not from ones you constructed.`);
|
|
76
|
+
}
|
|
77
|
+
return { updateTime };
|
|
78
|
+
}
|
|
79
|
+
/** "Only if this document exists" / "only if it does not." */
|
|
80
|
+
function documentExists(exists = true) {
|
|
81
|
+
return { exists };
|
|
82
|
+
}
|
|
83
|
+
/** Firestore treats these as a range scan, which constrains where they may sit. */
|
|
84
|
+
const RANGE_OPERATORS = new Set(["!=", "not-in", "<", "<=", ">", ">="]);
|
|
85
|
+
/** Served by an equality-shaped index entry - `in` is a disjunction of equalities. */
|
|
86
|
+
const EQUALITY_OPERATORS = new Set(["==", "in"]);
|
|
87
|
+
/**
|
|
88
|
+
* The composite index a query shape needs, or `null` when Firestore's
|
|
89
|
+
* automatic single-field indexes already serve it.
|
|
90
|
+
*
|
|
91
|
+
* Use it to generate your Store tab declarations from the queries your code
|
|
92
|
+
* actually makes, rather than transcribing them by hand and discovering the
|
|
93
|
+
* gap in production:
|
|
94
|
+
*
|
|
95
|
+
* ```ts
|
|
96
|
+
* const needed = MY_QUERY_SHAPES.map(storeIndexFor).filter(Boolean);
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* **Field order is Firestore's, not yours.** Equality fields first (sorted, so
|
|
100
|
+
* the order you passed them in cannot produce two different declarations for
|
|
101
|
+
* one query), then the `array-contains` field, then the field the query orders
|
|
102
|
+
* by. Declaring the same shape twice in two orders builds two indexes, one of
|
|
103
|
+
* which is dead weight on every document write.
|
|
104
|
+
*
|
|
105
|
+
* Connect runs the same derivation server-side and is the authority: a query
|
|
106
|
+
* with no matching declared index is refused with the index it needs spelled
|
|
107
|
+
* out, so a disagreement here surfaces on the first call rather than silently.
|
|
108
|
+
*
|
|
109
|
+
* Throws for a query Firestore cannot serve at all - two `array-contains`
|
|
110
|
+
* filters, ranges over two fields, or a range ordered by a different field.
|
|
111
|
+
*/
|
|
112
|
+
function storeIndexFor(query) {
|
|
113
|
+
const filters = query.filters ?? [];
|
|
114
|
+
const orderBy = query.orderBy ?? null;
|
|
115
|
+
const equalities = filters.filter((filter) => EQUALITY_OPERATORS.has(filter.op));
|
|
116
|
+
const arrayContains = filters.filter((filter) => filter.op === "array-contains");
|
|
117
|
+
const rangeFields = [
|
|
118
|
+
...new Set(filters.filter((filter) => RANGE_OPERATORS.has(filter.op)).map((f) => f.field)),
|
|
119
|
+
];
|
|
120
|
+
if (arrayContains.length > 1) {
|
|
121
|
+
throw new Error(`A query may use "array-contains" once; this one uses it ${arrayContains.length} times.`);
|
|
122
|
+
}
|
|
123
|
+
if (rangeFields.length > 1) {
|
|
124
|
+
throw new Error(`A query may range over one field; this one ranges over ${rangeFields.join(", ")}.`);
|
|
125
|
+
}
|
|
126
|
+
const rangeField = rangeFields[0] ?? null;
|
|
127
|
+
if (rangeField && orderBy && orderBy.field !== rangeField) {
|
|
128
|
+
throw new Error(`A range filter on "${rangeField}" must be ordered by "${rangeField}" - Firestore cannot order a range scan by "${orderBy.field}".`);
|
|
129
|
+
}
|
|
130
|
+
const ordered = orderBy?.field ?? rangeField;
|
|
131
|
+
const distinct = new Set([...filters.map((filter) => filter.field), ...(ordered ? [ordered] : [])]);
|
|
132
|
+
if (filters.length <= 1 && distinct.size <= 1)
|
|
133
|
+
return null;
|
|
134
|
+
const fields = [
|
|
135
|
+
...[...new Set(equalities.map((filter) => filter.field))]
|
|
136
|
+
.sort()
|
|
137
|
+
.map((field) => ({ field, direction: "asc" })),
|
|
138
|
+
...arrayContains.map((filter) => ({ field: filter.field, direction: "array-contains" })),
|
|
139
|
+
];
|
|
140
|
+
if (ordered)
|
|
141
|
+
fields.push({ field: ordered, direction: orderBy?.direction ?? "asc" });
|
|
142
|
+
return fields;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* An index as one comparable string - `"status:asc,createdAt:desc"`.
|
|
146
|
+
*
|
|
147
|
+
* Deduplicate a generated list on this before declaring it: a dozen query
|
|
148
|
+
* shapes across a screen usually collapse to two or three distinct indexes.
|
|
149
|
+
*/
|
|
150
|
+
function storeIndexSignature(fields) {
|
|
151
|
+
return fields.map((entry) => `${entry.field}:${entry.direction}`).join(",");
|
|
152
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectEmptyState = ConnectEmptyState;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@heroui/react");
|
|
6
|
+
/**
|
|
7
|
+
* What a list, grid or table shows when it has nothing to show.
|
|
8
|
+
*
|
|
9
|
+
* **The two empty states are different facts and the copy must say so.** "You
|
|
10
|
+
* have no contacts" and "no contacts match this filter" call for different
|
|
11
|
+
* words and a different action - create the thing, versus undo the filter
|
|
12
|
+
* hiding it. Both render through here; the caller decides which by what it
|
|
13
|
+
* passes as `action`. Getting this wrong costs a user real time: an
|
|
14
|
+
* unfiltered-looking empty state in front of a filtered list reads as data
|
|
15
|
+
* loss.
|
|
16
|
+
*
|
|
17
|
+
* `variant` chooses the container by what the state stands *in*: `card` (the
|
|
18
|
+
* default), `dashed` for a region that will be filled in place - a file grid,
|
|
19
|
+
* a board column - where the outline is what says "things appear here", and
|
|
20
|
+
* `plain` for a slot already inside a card that would otherwise nest two
|
|
21
|
+
* borders.
|
|
22
|
+
*/
|
|
23
|
+
function ConnectEmptyState({ icon, title, description, action, variant = "card", className, }) {
|
|
24
|
+
const body = ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col items-center text-center", children: [icon && ((0, jsx_runtime_1.jsx)("span", { "aria-hidden": true, className: "mb-3 text-muted [&>svg]:size-8", children: icon })), (0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: title }), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 max-w-md text-sm text-muted", children: description }), action && (0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: action })] }));
|
|
25
|
+
if (variant === "plain")
|
|
26
|
+
return (0, jsx_runtime_1.jsx)("div", { className: className, children: body });
|
|
27
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Card, { className: `p-5 sm:p-8 ${variant === "dashed" ? "border-dashed" : ""} ${className ?? ""}`, children: body }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Page-composition primitives - the layer between HeroUI and your features.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately **not** `"use client"`. Everything here is presentational, with
|
|
7
|
+
* no hooks and no state, so it renders in a Server Component and stays out of
|
|
8
|
+
* the browser bundle. That is the whole reason this is a separate entry point
|
|
9
|
+
* from `@cosmoops/connect-sdk/client`: that module's barrel carries a
|
|
10
|
+
* `"use client"` directive, so re-exporting a `<Card>` wrapper through it
|
|
11
|
+
* would turn every page shell in your app into a client boundary for no gain.
|
|
12
|
+
*
|
|
13
|
+
* The interactive counterparts - a confirm dialog, cursor pagination, filter
|
|
14
|
+
* tabs - hold state and live in `/client` alongside the rest of the browser
|
|
15
|
+
* half.
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* import { ConnectPageShell, ConnectSection } from "@cosmoops/connect-sdk/ui";
|
|
19
|
+
*
|
|
20
|
+
* export default function ContactsPage() {
|
|
21
|
+
* return (
|
|
22
|
+
* <ConnectPageShell title="Contacts" description="Everyone you do business with.">
|
|
23
|
+
* <ConnectPageShell.Async fallback={<TableSkeleton />}>
|
|
24
|
+
* <ContactsTable />
|
|
25
|
+
* </ConnectPageShell.Async>
|
|
26
|
+
* </ConnectPageShell>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.ConnectStatusChip = exports.ConnectSettingRow = exports.ConnectEmptyState = exports.ConnectSection = exports.ConnectPageHeader = exports.ConnectPageShell = void 0;
|
|
33
|
+
var page_shell_js_1 = require("./page-shell.js");
|
|
34
|
+
Object.defineProperty(exports, "ConnectPageShell", { enumerable: true, get: function () { return page_shell_js_1.ConnectPageShell; } });
|
|
35
|
+
var page_header_js_1 = require("./page-header.js");
|
|
36
|
+
Object.defineProperty(exports, "ConnectPageHeader", { enumerable: true, get: function () { return page_header_js_1.ConnectPageHeader; } });
|
|
37
|
+
var section_js_1 = require("./section.js");
|
|
38
|
+
Object.defineProperty(exports, "ConnectSection", { enumerable: true, get: function () { return section_js_1.ConnectSection; } });
|
|
39
|
+
var empty_state_js_1 = require("./empty-state.js");
|
|
40
|
+
Object.defineProperty(exports, "ConnectEmptyState", { enumerable: true, get: function () { return empty_state_js_1.ConnectEmptyState; } });
|
|
41
|
+
var setting_row_js_1 = require("./setting-row.js");
|
|
42
|
+
Object.defineProperty(exports, "ConnectSettingRow", { enumerable: true, get: function () { return setting_row_js_1.ConnectSettingRow; } });
|
|
43
|
+
var status_chip_js_1 = require("./status-chip.js");
|
|
44
|
+
Object.defineProperty(exports, "ConnectStatusChip", { enumerable: true, get: function () { return status_chip_js_1.ConnectStatusChip; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectPageHeader = ConnectPageHeader;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* The title / description / action block a page opens with.
|
|
7
|
+
*
|
|
8
|
+
* The structure is the point, not the styling. The action is a peer of the
|
|
9
|
+
* **title**, not of the block, so nothing below the first line can move it -
|
|
10
|
+
* the common hand-rolled shape puts the action after the description, which
|
|
11
|
+
* makes the vertical position of a page's primary control a function of how
|
|
12
|
+
* long someone wrote the paragraph above it. On a phone that pushes it most of
|
|
13
|
+
* a thumb's reach down the screen.
|
|
14
|
+
*
|
|
15
|
+
* Mobile-first, and the two axes are separate decisions: the title row is a
|
|
16
|
+
* `justify-between` at every width (title left, action right, where a thumb
|
|
17
|
+
* expects it), while the description is clamped to two lines on a phone and
|
|
18
|
+
* released from `sm` up.
|
|
19
|
+
*
|
|
20
|
+
* An `h2`, because the document's `h1` is the application itself.
|
|
21
|
+
*
|
|
22
|
+
* No hooks and no state, so this renders from a Server Component - which is
|
|
23
|
+
* why it lives in `@cosmoops/connect-sdk/ui` rather than `/client`.
|
|
24
|
+
*/
|
|
25
|
+
function ConnectPageHeader({ title, description, action, clamp = true, className, }) {
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)("header", { className: `flex items-start justify-between gap-3 ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("h2", { className: "flex items-center gap-2 text-lg font-semibold", children: title }), description && ((0, jsx_runtime_1.jsx)("p", { className: `mt-2 text-sm text-muted ${clamp ? "line-clamp-2 sm:line-clamp-none" : ""}`, children: description }))] }), action && (0, jsx_runtime_1.jsx)("div", { className: "shrink-0", children: action })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectPageShell = ConnectPageShell;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const page_header_js_1 = require("./page-header.js");
|
|
7
|
+
/**
|
|
8
|
+
* The frame a page opens with: its vertical rhythm, its header, and the
|
|
9
|
+
* suspended regions beneath.
|
|
10
|
+
*
|
|
11
|
+
* **The header is a prop, so it renders in the static shell** - above and
|
|
12
|
+
* outside every `<Suspense>` boundary. That is the one bug this component
|
|
13
|
+
* exists to make unrepresentable: rendering the header inside the async child
|
|
14
|
+
* makes a page's own title wait on a data read, and under Next's partial
|
|
15
|
+
* prefetching the route's shared App Shell loses it as well. There is no
|
|
16
|
+
* arrangement of `children` that reaches the header slot.
|
|
17
|
+
*
|
|
18
|
+
* A detail route is the honest exception, and it has its own door:
|
|
19
|
+
* `ConnectPageShell.Header` renders the identical header from inside an async
|
|
20
|
+
* child, for a page whose title is data it does not have yet. Pair it with a
|
|
21
|
+
* skeleton header in the fallback. The rule is that a *static* title must
|
|
22
|
+
* never be made to wait, not that every title can be static.
|
|
23
|
+
*
|
|
24
|
+
* `space-y-5` is the page gap, owned here so it has exactly one definition.
|
|
25
|
+
*/
|
|
26
|
+
function ConnectPageShell({ title, description, action, clamp, above, children, }) {
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-5", children: [above, title !== undefined && ((0, jsx_runtime_1.jsx)(page_header_js_1.ConnectPageHeader, { title: title, description: description, action: action, clamp: clamp })), children] }));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The page header, for a detail route that can only name itself once its data
|
|
31
|
+
* has loaded. The same component the shell renders from its props, so the two
|
|
32
|
+
* cannot drift apart.
|
|
33
|
+
*/
|
|
34
|
+
ConnectPageShell.Header = page_header_js_1.ConnectPageHeader;
|
|
35
|
+
/**
|
|
36
|
+
* A suspended region of the page.
|
|
37
|
+
*
|
|
38
|
+
* The donut: a static shell with async children behind their own boundaries,
|
|
39
|
+
* so a slow read costs its own section rather than the whole page. Give each
|
|
40
|
+
* independent region its own `Async` and they stream separately.
|
|
41
|
+
*/
|
|
42
|
+
ConnectPageShell.Async = function ConnectPageShellAsync({ fallback = null, children, }) {
|
|
43
|
+
return (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: fallback, children: children });
|
|
44
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectSection = ConnectSection;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@heroui/react");
|
|
6
|
+
/**
|
|
7
|
+
* One titled block inside a page: a card, its heading, what it is for, and the
|
|
8
|
+
* one control that acts on it.
|
|
9
|
+
*
|
|
10
|
+
* The heading is an `h3` - `ConnectPageHeader` renders the page title as an
|
|
11
|
+
* `h2`, so a block *inside* a page is a level below it. Hand-rolled sections
|
|
12
|
+
* reliably use `h2`, which makes every settings block a sibling of the page it
|
|
13
|
+
* sits in and flattens the outline a screen reader announces.
|
|
14
|
+
*
|
|
15
|
+
* As in the page header, the action is a peer of the title rather than of the
|
|
16
|
+
* block, so its position does not move with the length of the description.
|
|
17
|
+
*/
|
|
18
|
+
function ConnectSection({ title, description, action, titleId, className, bodyClassName, children, }) {
|
|
19
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Card, { className: className ?? "p-4 sm:p-5", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("h3", { id: titleId, className: "text-base font-semibold", children: title }), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-muted", children: description })] }), action && (0, jsx_runtime_1.jsx)("div", { className: "shrink-0", children: action })] }), children && (0, jsx_runtime_1.jsx)("div", { className: bodyClassName ?? "mt-4 space-y-4", children: children })] }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectSettingRow = ConnectSettingRow;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
/**
|
|
6
|
+
* One labelled setting inside a `ConnectSection`.
|
|
7
|
+
*
|
|
8
|
+
* Stacks on a phone and goes side-by-side from `sm` up, rather than shrinking
|
|
9
|
+
* a two-column row until neither column is readable. The control keeps its
|
|
10
|
+
* intrinsic width so truncation lands on the description.
|
|
11
|
+
*/
|
|
12
|
+
function ConnectSettingRow({ title, description, action, children, className, }) {
|
|
13
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between ${className ?? ""}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: title }), description && (0, jsx_runtime_1.jsx)("p", { className: "mt-1 text-sm text-muted", children: description }), children && (0, jsx_runtime_1.jsx)("div", { className: "mt-3", children: children })] }), action && (0, jsx_runtime_1.jsx)("div", { className: "shrink-0 sm:pl-4", children: action })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectStatusChip = ConnectStatusChip;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("@heroui/react");
|
|
6
|
+
/**
|
|
7
|
+
* One status, rendered the way every status should be.
|
|
8
|
+
*
|
|
9
|
+
* Domain knowledge stays in the domain: each caller exports its own
|
|
10
|
+
* `ConnectStatusPresentation` next to the union it describes, and this renders
|
|
11
|
+
* it. A status whose *rendering* carries real logic - a scheduled cancellation
|
|
12
|
+
* outranking a running trial, say - keeps its own component and calls this for
|
|
13
|
+
* the final chip. A map cannot express precedence.
|
|
14
|
+
*/
|
|
15
|
+
function ConnectStatusChip({ status, presentation, className, }) {
|
|
16
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Chip, { color: presentation.tone[status], size: "sm", variant: "soft", className: className, children: presentation.label[status] }));
|
|
17
|
+
}
|
package/dist/esm/cli/doctor.js
CHANGED
|
@@ -159,8 +159,8 @@ function checkEnv(root) {
|
|
|
159
159
|
status: "fail",
|
|
160
160
|
title: `${name} set`,
|
|
161
161
|
detail: declaredEmpty
|
|
162
|
-
? `Declared but empty in .env.local. The value is in your application's
|
|
163
|
-
: `Not found in .env.local or the environment. Both values are in your application's
|
|
162
|
+
? `Declared but empty in .env.local. The value is in your application's Connect client section in Connect.`
|
|
163
|
+
: `Not found in .env.local or the environment. Both values are in your application's Connect client section in Connect.`,
|
|
164
164
|
};
|
|
165
165
|
});
|
|
166
166
|
}
|
|
@@ -173,7 +173,7 @@ function checkRedirectUri() {
|
|
|
173
173
|
return {
|
|
174
174
|
status: "warn",
|
|
175
175
|
title: "Redirect URI registered in Connect",
|
|
176
|
-
detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's
|
|
176
|
+
detail: "Not checkable from here. Confirm {your origin}/api/connect/callback is listed as a redirect URI in your application's Connect client section - including the deployed origin, not just http://localhost:3000.",
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
179
|
const MARKER = { pass: " ok ", warn: " note ", fail: " FAIL " };
|
package/dist/esm/cli/init.js
CHANGED
|
@@ -39,7 +39,7 @@ const PLANS_PAGE = `"use client";
|
|
|
39
39
|
// with the other exports) whenever you want your own copy or layout.
|
|
40
40
|
export { ConnectPlansPage as default } from "@cosmoops/connect-sdk/client";
|
|
41
41
|
`;
|
|
42
|
-
const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's
|
|
42
|
+
const ENV_TEMPLATE = `# CosmoOps Connect - values come from your application's Connect client section.
|
|
43
43
|
CONNECT_API_KEY=
|
|
44
44
|
CONNECT_CLIENT_ID=
|
|
45
45
|
`;
|
|
@@ -106,7 +106,7 @@ export function runInit(argv = []) {
|
|
|
106
106
|
console.log(` ${marker} ${relative(root, target)} - ${scaffold.description}`);
|
|
107
107
|
}
|
|
108
108
|
const env = ensureEnv(root);
|
|
109
|
-
console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's
|
|
109
|
+
console.log(` ${env === "skipped" ? "exists " : env === "appended" ? "updated" : "created"} .env.local - fill in the two values from your application's Connect client section`);
|
|
110
110
|
// The two steps a scaffolder must not do for you: editing an existing layout
|
|
111
111
|
// means parsing and rewriting JSX someone else wrote, and the redirect URI
|
|
112
112
|
// lives in Connect, not in this repo.
|
|
@@ -132,7 +132,7 @@ Three things left, all one-time:
|
|
|
132
132
|
Tailwind roots its content scan inside node_modules and skips your app.
|
|
133
133
|
|
|
134
134
|
3. Register {your origin}/api/connect/callback as a redirect URI in your
|
|
135
|
-
application's
|
|
135
|
+
application's Connect client section in Connect.
|
|
136
136
|
|
|
137
137
|
No next.config.ts changes needed - not even transpilePackages: dist/ is
|
|
138
138
|
already ES2022 with "use client" hoisted, so Turbopack and webpack consume it
|
|
@@ -36,12 +36,31 @@ function formatDate(iso) {
|
|
|
36
36
|
export function useConnectAccessGate() {
|
|
37
37
|
const { session, isLoading: sessionLoading } = useConnect();
|
|
38
38
|
const { data, isLoading: entitlementsLoading } = useConnectEntitlements();
|
|
39
|
-
if (sessionLoading
|
|
39
|
+
if (sessionLoading) {
|
|
40
40
|
return { state: "loading", blocker: null, paymentRetry: null };
|
|
41
41
|
}
|
|
42
42
|
if (!session?.user) {
|
|
43
43
|
return { state: "signed_out", blocker: null, paymentRetry: null };
|
|
44
44
|
}
|
|
45
|
+
// Connect platform staff pass straight through, and this check has to come
|
|
46
|
+
// *before* the entitlements read below or the gate would hard-block them.
|
|
47
|
+
//
|
|
48
|
+
// Every state this gate can report is a fact about a workspace - suspended,
|
|
49
|
+
// no plan, wrong audience, awaiting approval, payment due. A staff session
|
|
50
|
+
// has no workspace at all (see `SessionAccess.staff`), so it has no subscription
|
|
51
|
+
// either, and `subscription_missing` would send an operator to your plan
|
|
52
|
+
// picker to buy a plan for a workspace that does not exist. "Nothing is
|
|
53
|
+
// blocking this session" is the truthful answer, not a lenient one.
|
|
54
|
+
//
|
|
55
|
+
// The entitlements request is still issued - hooks cannot be called
|
|
56
|
+
// conditionally - and the bridge answers it `403 no_workspace`. That costs
|
|
57
|
+
// one failed request per staff page load, and its result is ignored here.
|
|
58
|
+
if (session.staff) {
|
|
59
|
+
return { state: "clear", blocker: null, paymentRetry: null };
|
|
60
|
+
}
|
|
61
|
+
if (entitlementsLoading) {
|
|
62
|
+
return { state: "loading", blocker: null, paymentRetry: null };
|
|
63
|
+
}
|
|
45
64
|
if (!data) {
|
|
46
65
|
// Signed in, not loading, yet nothing came back - a failed read (see
|
|
47
66
|
// `error` on `useConnectEntitlements`). Fail open rather than hard-block
|