@cosmoops/connect-sdk 0.25.0 → 0.26.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 +72 -0
- package/dist/cjs/client/components/access-gate.js +31 -5
- package/dist/cjs/client/components/account-sessions.js +4 -3
- package/dist/cjs/client/components/app-shell.js +2 -1
- package/dist/cjs/client/components/booking-calendar.js +2 -1
- package/dist/cjs/client/components/cursor-pagination.js +3 -2
- package/dist/cjs/client/components/filter-tabs.js +23 -5
- package/dist/cjs/client/components/grievance-notice.js +2 -1
- package/dist/cjs/client/components/logo.js +3 -2
- package/dist/cjs/client/components/one-tap.js +2 -1
- package/dist/cjs/client/components/plans.js +3 -2
- package/dist/cjs/client/components/readiness.js +4 -3
- package/dist/cjs/client/components/shell-context.js +2 -1
- package/dist/cjs/client/components/side-nav.js +2 -1
- package/dist/cjs/client/components/sidebar.js +2 -1
- package/dist/cjs/client/components/subscription.js +5 -4
- package/dist/cjs/client/components/usage-meter.js +2 -1
- package/dist/cjs/client/components/user-menu.js +3 -2
- package/dist/cjs/cn.js +53 -0
- package/dist/cjs/server/client.js +1 -1
- package/dist/cjs/store.js +14 -1
- package/dist/cjs/ui/empty-state.js +2 -1
- package/dist/cjs/ui/index.js +8 -1
- package/dist/cjs/ui/page-header.js +2 -1
- package/dist/cjs/ui/page-shell.js +3 -2
- package/dist/cjs/ui/section.js +2 -1
- package/dist/cjs/ui/setting-row.js +2 -1
- package/dist/esm/client/components/access-gate.js +33 -7
- package/dist/esm/client/components/account-sessions.js +4 -3
- package/dist/esm/client/components/app-shell.js +2 -1
- package/dist/esm/client/components/booking-calendar.js +2 -1
- package/dist/esm/client/components/cursor-pagination.js +3 -2
- package/dist/esm/client/components/filter-tabs.js +23 -5
- package/dist/esm/client/components/grievance-notice.js +2 -1
- package/dist/esm/client/components/logo.js +3 -2
- package/dist/esm/client/components/one-tap.js +2 -1
- package/dist/esm/client/components/plans.js +3 -2
- package/dist/esm/client/components/readiness.js +4 -3
- package/dist/esm/client/components/shell-context.js +2 -1
- package/dist/esm/client/components/side-nav.js +2 -1
- package/dist/esm/client/components/sidebar.js +2 -1
- package/dist/esm/client/components/subscription.js +5 -4
- package/dist/esm/client/components/usage-meter.js +2 -1
- package/dist/esm/client/components/user-menu.js +3 -2
- package/dist/esm/cn.js +48 -0
- package/dist/esm/server/client.js +1 -1
- package/dist/esm/store.js +14 -1
- package/dist/esm/ui/empty-state.js +2 -1
- package/dist/esm/ui/index.js +6 -0
- package/dist/esm/ui/page-header.js +2 -1
- package/dist/esm/ui/page-shell.js +3 -2
- package/dist/esm/ui/section.js +2 -1
- package/dist/esm/ui/setting-row.js +2 -1
- package/dist/theme.css +53 -0
- package/dist/types/client/components/access-gate.d.ts +35 -4
- package/dist/types/client/components/cursor-pagination.d.ts +6 -1
- package/dist/types/client/components/filter-tabs.d.ts +9 -3
- package/dist/types/cn.d.ts +48 -0
- package/dist/types/server/client.d.ts +1 -1
- package/dist/types/store.d.ts +6 -0
- package/dist/types/ui/index.d.ts +6 -0
- package/dist/types/ui/page-shell.d.ts +7 -1
- package/package.json +1 -1
package/dist/cjs/cn.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.cn = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* Merge class names so a caller's Tailwind utility actually wins.
|
|
7
|
+
*
|
|
8
|
+
* Every component in this package carries default utilities, and a partner
|
|
9
|
+
* styling one reaches for `className`. Concatenating the two does not do what
|
|
10
|
+
* it looks like it does:
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* // ConnectBookingCalendar with className="gap-8"
|
|
14
|
+
* <div className={`flex flex-col gap-4 ${className}`} />
|
|
15
|
+
* // -> class="flex flex-col gap-4 gap-8"
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Both rules are now in the stylesheet and both match, so the winner is
|
|
19
|
+
* decided by **source order in the generated CSS**, not by the order they
|
|
20
|
+
* appear in the attribute. Tailwind sorts its output by its own internal
|
|
21
|
+
* ordering, so whether `gap-8` beats `gap-4` depends on which utility Tailwind
|
|
22
|
+
* happens to emit later - `p-8` over `px-3 py-2` lands one way, `gap-8` over
|
|
23
|
+
* `gap-4` the other. An override that works on one prop and silently does
|
|
24
|
+
* nothing on the next is worse than one that never works, because nobody
|
|
25
|
+
* suspects the mechanism.
|
|
26
|
+
*
|
|
27
|
+
* `cn` resolves it by *removing* the conflicting default rather than adding to
|
|
28
|
+
* it - `cn("flex flex-col gap-4", "gap-8")` is `"flex flex-col gap-8"`, and
|
|
29
|
+
* `cn("px-3 py-2", "p-8")` is `"p-8"`. That is a real understanding of
|
|
30
|
+
* Tailwind's shorthand groups, which is why it is `tailwind-merge` and not a
|
|
31
|
+
* `join(" ")`.
|
|
32
|
+
*
|
|
33
|
+
* Re-exported rather than reimplemented. The implementation is
|
|
34
|
+
* `tailwind-variants`' `cn`, which `@heroui/react` re-exports and which every
|
|
35
|
+
* HeroUI component uses internally through `tv` - so a partner's override is
|
|
36
|
+
* resolved by exactly the same rules whether it lands on one of these
|
|
37
|
+
* components or on a HeroUI one. It is reached through `@heroui/react` rather
|
|
38
|
+
* than `tailwind-variants` directly because HeroUI is already a declared peer
|
|
39
|
+
* of both UI entry points, and importing a package this manifest does not name
|
|
40
|
+
* would be relying on someone else's transitive tree.
|
|
41
|
+
*
|
|
42
|
+
* Safe in the server-rendered `./ui` entry: the `@heroui/react` barrel carries
|
|
43
|
+
* no top-level `"use client"` (only its individual components do), so `cn`
|
|
44
|
+
* arrives as a real function rather than a client-reference proxy. That
|
|
45
|
+
* distinction matters for a *function* in a way it does not for a component -
|
|
46
|
+
* `ui/section.tsx` has always imported `Card` from the same barrel.
|
|
47
|
+
*
|
|
48
|
+
* Not exported from the root entry, which is isomorphic and React-free. It is
|
|
49
|
+
* public from `./ui`, because a partner composing on top of these primitives
|
|
50
|
+
* needs the same merge the primitives use.
|
|
51
|
+
*/
|
|
52
|
+
var react_1 = require("@heroui/react");
|
|
53
|
+
Object.defineProperty(exports, "cn", { enumerable: true, get: function () { return react_1.cn; } });
|
package/dist/cjs/store.js
CHANGED
|
@@ -108,6 +108,12 @@ const EQUALITY_OPERATORS = new Set(["==", "in"]);
|
|
|
108
108
|
*
|
|
109
109
|
* Throws for a query Firestore cannot serve at all - two `array-contains`
|
|
110
110
|
* filters, ranges over two fields, or a range ordered by a different field.
|
|
111
|
+
*
|
|
112
|
+
* A **range bounded on both sides** over one field needs no declaration: two
|
|
113
|
+
* filters, one field, and the automatic single-field index serves it. Ask this
|
|
114
|
+
* function with the filters you actually send rather than with one of the two
|
|
115
|
+
* bounds - the answer is the same, and modelling the range as a single `>=`
|
|
116
|
+
* is how a schema comes to declare a shape its own runtime does not make.
|
|
111
117
|
*/
|
|
112
118
|
function storeIndexFor(query) {
|
|
113
119
|
const filters = query.filters ?? [];
|
|
@@ -129,7 +135,14 @@ function storeIndexFor(query) {
|
|
|
129
135
|
}
|
|
130
136
|
const ordered = orderBy?.field ?? rangeField;
|
|
131
137
|
const distinct = new Set([...filters.map((filter) => filter.field), ...(ordered ? [ordered] : [])]);
|
|
132
|
-
|
|
138
|
+
// Distinct *fields*, not filter count. A two-sided range over one field -
|
|
139
|
+
// `day >= from AND day <= to ORDER BY day`, the shape of every date window -
|
|
140
|
+
// is exactly what an automatic single-field index is for. Counting filters
|
|
141
|
+
// instead answered `[day asc]`: an index Firestore will not build, since a
|
|
142
|
+
// composite needs two fields, and which is by definition the automatic one
|
|
143
|
+
// already serving the query. `array-contains` is the exception, because an
|
|
144
|
+
// array field's automatic index does not serve an inequality on that field.
|
|
145
|
+
if (distinct.size <= 1 && (arrayContains.length === 0 || filters.length <= 1))
|
|
133
146
|
return null;
|
|
134
147
|
const fields = [
|
|
135
148
|
...[...new Set(equalities.map((filter) => filter.field))]
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ConnectEmptyState = ConnectEmptyState;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("@heroui/react");
|
|
6
|
+
const cn_js_1 = require("../cn.js");
|
|
6
7
|
/**
|
|
7
8
|
* What a list, grid or table shows when it has nothing to show.
|
|
8
9
|
*
|
|
@@ -24,5 +25,5 @@ function ConnectEmptyState({ icon, title, description, action, variant = "card",
|
|
|
24
25
|
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
26
|
if (variant === "plain")
|
|
26
27
|
return (0, jsx_runtime_1.jsx)("div", { className: className, children: body });
|
|
27
|
-
return ((0, jsx_runtime_1.jsx)(react_1.Card, { className:
|
|
28
|
+
return ((0, jsx_runtime_1.jsx)(react_1.Card, { className: (0, cn_js_1.cn)("p-5 sm:p-8", variant === "dashed" ? "border-dashed" : "", className), children: body }));
|
|
28
29
|
}
|
package/dist/cjs/ui/index.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.ConnectStatusChip = exports.ConnectSettingRow = exports.ConnectEmptyState = exports.ConnectSection = exports.ConnectPageHeader = exports.ConnectPageShell = void 0;
|
|
32
|
+
exports.cn = exports.ConnectStatusChip = exports.ConnectSettingRow = exports.ConnectEmptyState = exports.ConnectSection = exports.ConnectPageHeader = exports.ConnectPageShell = void 0;
|
|
33
33
|
var page_shell_js_1 = require("./page-shell.js");
|
|
34
34
|
Object.defineProperty(exports, "ConnectPageShell", { enumerable: true, get: function () { return page_shell_js_1.ConnectPageShell; } });
|
|
35
35
|
var page_header_js_1 = require("./page-header.js");
|
|
@@ -42,3 +42,10 @@ var setting_row_js_1 = require("./setting-row.js");
|
|
|
42
42
|
Object.defineProperty(exports, "ConnectSettingRow", { enumerable: true, get: function () { return setting_row_js_1.ConnectSettingRow; } });
|
|
43
43
|
var status_chip_js_1 = require("./status-chip.js");
|
|
44
44
|
Object.defineProperty(exports, "ConnectStatusChip", { enumerable: true, get: function () { return status_chip_js_1.ConnectStatusChip; } });
|
|
45
|
+
/**
|
|
46
|
+
* Merge class names the way these components do internally, so a utility you
|
|
47
|
+
* pass wins over a conflicting default instead of racing it in the stylesheet.
|
|
48
|
+
* Use it when composing your own components on top of these.
|
|
49
|
+
*/
|
|
50
|
+
var cn_js_1 = require("../cn.js");
|
|
51
|
+
Object.defineProperty(exports, "cn", { enumerable: true, get: function () { return cn_js_1.cn; } });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConnectPageHeader = ConnectPageHeader;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const cn_js_1 = require("../cn.js");
|
|
5
6
|
/**
|
|
6
7
|
* The title / description / action block a page opens with.
|
|
7
8
|
*
|
|
@@ -23,5 +24,5 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
23
24
|
* why it lives in `@cosmoops/connect-sdk/ui` rather than `/client`.
|
|
24
25
|
*/
|
|
25
26
|
function ConnectPageHeader({ title, description, action, clamp = true, className, }) {
|
|
26
|
-
return ((0, jsx_runtime_1.jsxs)("header", { className:
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)("header", { className: (0, cn_js_1.cn)("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
28
|
}
|
|
@@ -4,6 +4,7 @@ exports.ConnectPageShell = ConnectPageShell;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const page_header_js_1 = require("./page-header.js");
|
|
7
|
+
const cn_js_1 = require("../cn.js");
|
|
7
8
|
/**
|
|
8
9
|
* The frame a page opens with: its vertical rhythm, its header, and the
|
|
9
10
|
* suspended regions beneath.
|
|
@@ -23,8 +24,8 @@ const page_header_js_1 = require("./page-header.js");
|
|
|
23
24
|
*
|
|
24
25
|
* `space-y-5` is the page gap, owned here so it has exactly one definition.
|
|
25
26
|
*/
|
|
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] }));
|
|
27
|
+
function ConnectPageShell({ title, description, action, clamp, above, children, className, }) {
|
|
28
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("space-y-5", className), 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
|
}
|
|
29
30
|
/**
|
|
30
31
|
* The page header, for a detail route that can only name itself once its data
|
package/dist/cjs/ui/section.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ConnectSection = ConnectSection;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("@heroui/react");
|
|
6
|
+
const cn_js_1 = require("../cn.js");
|
|
6
7
|
/**
|
|
7
8
|
* One titled block inside a page: a card, its heading, what it is for, and the
|
|
8
9
|
* one control that acts on it.
|
|
@@ -16,5 +17,5 @@ const react_1 = require("@heroui/react");
|
|
|
16
17
|
* block, so its position does not move with the length of the description.
|
|
17
18
|
*/
|
|
18
19
|
function ConnectSection({ title, description, action, titleId, className, bodyClassName, children, }) {
|
|
19
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Card, { className:
|
|
20
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Card, { className: (0, cn_js_1.cn)("p-4 sm:p-5", className), 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
21
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConnectSettingRow = ConnectSettingRow;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const cn_js_1 = require("../cn.js");
|
|
5
6
|
/**
|
|
6
7
|
* One labelled setting inside a `ConnectSection`.
|
|
7
8
|
*
|
|
@@ -10,5 +11,5 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
10
11
|
* intrinsic width so truncation lands on the description.
|
|
11
12
|
*/
|
|
12
13
|
function ConnectSettingRow({ title, description, action, children, className, }) {
|
|
13
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className:
|
|
14
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("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
15
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Ban, AlertTriangle, Building2, Clock, CreditCard, ExternalLink, User, X } from "lucide-react";
|
|
5
|
-
import { Button, Card
|
|
5
|
+
import { Button, Card } from "@heroui/react";
|
|
6
6
|
import { blockingBlockers } from "../../helpers.js";
|
|
7
7
|
import { useConnect, useConnectEntitlements } from "../provider.js";
|
|
8
8
|
import { ENTER, m, MotionProvider } from "./motion.js";
|
|
9
9
|
import { ConnectReadinessBanner } from "./readiness.js";
|
|
10
|
+
import { cn } from "../../cn.js";
|
|
10
11
|
function formatDate(iso) {
|
|
11
12
|
return new Date(iso).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric" });
|
|
12
13
|
}
|
|
@@ -117,15 +118,40 @@ export function useConnectAccessGate() {
|
|
|
117
118
|
* entire app must not hide a public landing page behind an empty gate. Your
|
|
118
119
|
* own sign-in flow decides what a signed-out visitor sees.
|
|
119
120
|
*
|
|
121
|
+
* **While the verdict is unknown, `children` render.** The two reads behind
|
|
122
|
+
* this gate are client-side round trips that happen on *every* page load, and
|
|
123
|
+
* this component is mounted around an entire application - so blocking on
|
|
124
|
+
* them replaced every partner's whole page with one placeholder until both
|
|
125
|
+
* came back. Anything the host had streamed underneath was discarded: per-
|
|
126
|
+
* section skeletons never painted, and a page built as a static shell with
|
|
127
|
+
* suspended regions arrived all at once anyway. That is the common path, paid
|
|
128
|
+
* by every workspace on every navigation.
|
|
129
|
+
*
|
|
130
|
+
* Rendering `children` instead makes the loading case agree with the two
|
|
131
|
+
* cases either side of it. A *failed* read already falls through to `clear`
|
|
132
|
+
* ("fail open rather than hard-block the whole app on a network hiccup", in
|
|
133
|
+
* `useConnectAccessGate`), and a signed-out visitor already renders
|
|
134
|
+
* `children` too - "we do not know yet" was the one unknown treated as a
|
|
135
|
+
* refusal.
|
|
136
|
+
*
|
|
137
|
+
* What it costs is honest and small: a genuinely blocked workspace sees its
|
|
138
|
+
* app for the length of one round trip before the block card replaces it.
|
|
139
|
+
* That is the rare path, it is self-correcting, and it is the same exposure
|
|
140
|
+
* the failed-read branch has always accepted. Pass `loading` to opt back into
|
|
141
|
+
* a placeholder for a gate around one small region.
|
|
142
|
+
*
|
|
120
143
|
* **This is a UX gate, not a security boundary** - same caveat as every
|
|
121
|
-
* other gate in this package
|
|
122
|
-
* `client.entitlements.getReadiness` on your
|
|
123
|
-
* thing.
|
|
144
|
+
* other gate in this package, and the reason the trade above is available at
|
|
145
|
+
* all. The authoritative check is `client.entitlements.getReadiness` on your
|
|
146
|
+
* server, before you do the thing.
|
|
124
147
|
*/
|
|
125
148
|
export function ConnectAccessGate({ children, fallback, loading, className }) {
|
|
126
149
|
const gate = useConnectAccessGate();
|
|
150
|
+
// `children`, unless the host explicitly asked for a placeholder. See the
|
|
151
|
+
// doc comment - this is the branch that decides whether a partner's page
|
|
152
|
+
// can stream.
|
|
127
153
|
if (gate.state === "loading") {
|
|
128
|
-
return _jsx(_Fragment, { children: loading ??
|
|
154
|
+
return _jsx(_Fragment, { children: loading ?? children });
|
|
129
155
|
}
|
|
130
156
|
if (gate.state === "signed_out" || gate.state === "clear") {
|
|
131
157
|
return _jsx(_Fragment, { children: children });
|
|
@@ -204,7 +230,7 @@ function PaymentRetryBanner({ blocker, className, }) {
|
|
|
204
230
|
const [dismissed, setDismissed] = useState(() => dismissKey !== null && typeof window !== "undefined" && window.localStorage.getItem(dismissKey) === "1");
|
|
205
231
|
if (dismissed)
|
|
206
232
|
return null;
|
|
207
|
-
return (_jsx(MotionProvider, { children: _jsxs(m.div, { className:
|
|
233
|
+
return (_jsx(MotionProvider, { children: _jsxs(m.div, { className: cn("flex flex-wrap items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", className), role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: ENTER, children: [_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" }), blocker.message, blocker.graceEndsAt && ` Resolve by ${formatDate(blocker.graceEndsAt)} to avoid losing access.`] }), _jsxs("div", { className: "flex items-center gap-2", children: [blocker.resolveUrl && (_jsxs(Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: ["Resolve payment", _jsx(ExternalLink, { className: "size-3.5" })] })), _jsx(Button, { variant: "tertiary", isIconOnly: true, "aria-label": "Dismiss", onPress: () => {
|
|
208
234
|
setDismissed(true);
|
|
209
235
|
if (dismissKey && typeof window !== "undefined")
|
|
210
236
|
window.localStorage.setItem(dismissKey, "1");
|
|
@@ -7,6 +7,7 @@ import { useConnect, useConnectSessions } from "../provider.js";
|
|
|
7
7
|
import { AnimatePresence, EASE_OUT_QUART, m, MotionProvider } from "./motion.js";
|
|
8
8
|
import { ICON, PANEL_FILL } from "./surfaces.js";
|
|
9
9
|
import { formatRelativeTime } from "../../format-relative-time.js";
|
|
10
|
+
import { cn } from "../../cn.js";
|
|
10
11
|
const DEVICE_ICONS = {
|
|
11
12
|
desktop: Monitor,
|
|
12
13
|
mobile: Smartphone,
|
|
@@ -77,13 +78,13 @@ export function ConnectAccountSessions({ className, maxVisible = 4 }) {
|
|
|
77
78
|
const [isRevokingOthers, setIsRevokingOthers] = useState(false);
|
|
78
79
|
const [showAll, setShowAll] = useState(false);
|
|
79
80
|
if (isLoading) {
|
|
80
|
-
return (_jsxs("section", { className:
|
|
81
|
+
return (_jsxs("section", { className: cn("flex flex-col gap-3", className), children: [_jsx(Skeleton, { className: "h-4 w-28" }), _jsx("div", { className: `flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border ${PANEL_FILL}`, children: Array.from({ length: 2 }, (_, index) => (_jsxs("div", { className: "flex items-center gap-3 px-3 py-3", children: [_jsx(Skeleton, { className: "size-9 shrink-0 rounded-xl" }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3.5 w-32" }), _jsx(Skeleton, { className: "h-3 w-24" })] })] }, index))) })] }));
|
|
81
82
|
}
|
|
82
83
|
// A failed read used to render as *nothing*, which is indistinguishable
|
|
83
84
|
// from "you have no other devices" - the one reading of this list that
|
|
84
85
|
// should never be guessed at.
|
|
85
86
|
if (error) {
|
|
86
|
-
return (_jsxs("section", { className:
|
|
87
|
+
return (_jsxs("section", { className: cn("flex flex-col gap-2", className), children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "Active sessions" }), _jsx("p", { className: `rounded-xl border border-border ${PANEL_FILL} px-3 py-4 text-xs text-muted`, children: "Could not load your sessions. Try again in a moment." })] }));
|
|
87
88
|
}
|
|
88
89
|
const sessions = ordered(data?.items ?? []);
|
|
89
90
|
if (sessions.length === 0)
|
|
@@ -91,7 +92,7 @@ export function ConnectAccountSessions({ className, maxVisible = 4 }) {
|
|
|
91
92
|
const hasOtherSessions = sessions.some((session) => !session.isCurrent);
|
|
92
93
|
const isCollapsible = maxVisible > 0 && sessions.length > maxVisible;
|
|
93
94
|
const visible = isCollapsible && !showAll ? sessions.slice(0, maxVisible) : sessions;
|
|
94
|
-
return (_jsx(MotionProvider, { children: _jsxs("section", { className:
|
|
95
|
+
return (_jsx(MotionProvider, { children: _jsxs("section", { className: cn("flex flex-col gap-3", className), children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("p", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: ["Active sessions", _jsx(Chip, { size: "sm", variant: "soft", children: sessions.length })] }), _jsx("p", { className: "text-xs text-muted", children: "Devices signed in to your Connect account." })] }), hasOtherSessions && (_jsx(Button, { size: "sm", variant: "tertiary", className: "shrink-0 text-xs text-danger", isPending: isRevokingOthers, isDisabled: isRevokingOthers, onPress: () => {
|
|
95
96
|
setIsRevokingOthers(true);
|
|
96
97
|
void mutateResource("/sessions/revoke-others")
|
|
97
98
|
.then(() => {
|
|
@@ -17,6 +17,7 @@ import { ConnectWorkspaceSwitcher } from "./workspace-switcher.js";
|
|
|
17
17
|
import { ConnectUserButton } from "./user-menu.js";
|
|
18
18
|
import { filterNavForAudience, gateExemptPaths, normalizePath, } from "./nav.js";
|
|
19
19
|
import { useActivePath } from "../use-active-path.js";
|
|
20
|
+
import { cn } from "../../cn.js";
|
|
20
21
|
const COLLAPSED_KEY = "connect_sidenav_collapsed";
|
|
21
22
|
const COLLAPSED_EVENT = "connect:sidenav-collapsed";
|
|
22
23
|
/**
|
|
@@ -185,5 +186,5 @@ export function AppShell({ name, icon, homeHref = "/", nav, secondaryNav, active
|
|
|
185
186
|
}), [renderLink, activePath, closeMobile]);
|
|
186
187
|
const accountRow = signedOut ? (showAuthButtons && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(ConnectLoginButton, { size: "sm", fullWidth: true }), _jsx(ConnectRegisterButton, { size: "sm", fullWidth: true })] }))) : (_jsx(ConnectUserButton, { variant: "row", theme: theme, onThemeChange: onThemeChange }));
|
|
187
188
|
const railContent = (_jsxs(_Fragment, { children: [_jsx(ConnectSidebarHeader, { name: name, icon: icon, homeHref: homeHref }), workspaceSwitcher !== false && (_jsx("div", { className: "shrink-0 px-3 pb-1", children: _jsx(ConnectWorkspaceSwitcher, {}) })), _jsx(ConnectSidebarBody, { children: _jsx(ConnectSidebarNav, { nav: visibleNav }) }), (visibleSecondaryNav || sidebarFooter || showAppsGrid) && (_jsxs(ConnectSidebarFooter, { separated: false, children: [visibleSecondaryNav && _jsx(ConnectSidebarNav, { nav: visibleSecondaryNav }), sidebarFooter, showAppsGrid && _jsx(ConnectAppsGrid, { variant: "row" })] })), accountRow && _jsx(ConnectSidebarFooter, { children: accountRow })] }));
|
|
188
|
-
return (_jsx(MotionProvider, { children: _jsxs("div", { className:
|
|
189
|
+
return (_jsx(MotionProvider, { children: _jsxs("div", { className: cn("flex h-screen bg-background", className), children: [_jsx(ConnectShellProvider, { value: railShell, children: _jsx(ConnectSidebar, { children: railContent }) }), _jsx(Drawer.Backdrop, { isOpen: mobileOpen, onOpenChange: setMobileOpen, children: _jsx(Drawer.Content, { placement: "left", className: "w-[min(20rem,85vw)]", children: _jsx(Drawer.Dialog, { "aria-label": "Navigation menu", className: "p-0", children: _jsx(ConnectShellProvider, { value: drawerShell, children: _jsx("div", { className: "flex h-full flex-col", children: railContent }) }) }) }) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsxs("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b border-border bg-surface px-3 md:hidden", children: [_jsx(Button, { isIconOnly: true, variant: "tertiary", size: "sm", "aria-label": "Open navigation menu", onPress: () => setMobileOpen(true), children: _jsx(Menu, { className: "size-5" }) }), _jsx(ConnectLogo, { name: name, icon: icon })] }), _jsx("main", { className: "flex-1 overflow-y-auto", children: _jsxs("div", { className: contentClassName ?? "w-full px-4 py-5 md:px-6 md:py-6", children: [gated ? _jsx(ConnectAccessGate, { children: children }) : children, footer] }) })] }), askAi && _jsx(ConnectAskAi, { ...(askAi === true ? {} : askAi) })] }) }));
|
|
189
190
|
}
|
|
@@ -7,6 +7,7 @@ import { ConnectEmptyState } from "../../ui/empty-state.js";
|
|
|
7
7
|
import { useBookableEventTypes, useBookings, } from "../use-scheduling.js";
|
|
8
8
|
import { dayColumns, dayStartInstant, hourMarks, layoutBookings, zonedInstant, } from "./booking-grid.js";
|
|
9
9
|
import { ConnectBookingDetail, ConnectBookingDialog } from "./booking-dialogs.js";
|
|
10
|
+
import { cn } from "../../cn.js";
|
|
10
11
|
/**
|
|
11
12
|
* The visible band of the day. Outside it a booking is drawn at the edge and
|
|
12
13
|
* flagged rather than hidden - see `layoutBookings`.
|
|
@@ -64,7 +65,7 @@ export function ConnectBookingCalendar({ timeZone, hostMemberId, window: visible
|
|
|
64
65
|
const marks = useMemo(() => hourMarks(visibleWindow), [visibleWindow]);
|
|
65
66
|
const shiftWeek = (weeks) => setWeekAnchor((anchor) => new Date(anchor.getTime() + weeks * 7 * 24 * 60 * 60_000));
|
|
66
67
|
const eventTypeById = useMemo(() => new Map(eventTypes.map((eventType) => [eventType.id, eventType])), [eventTypes]);
|
|
67
|
-
return (_jsxs("div", { className:
|
|
68
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-4", className), children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Previous week", onPress: () => shiftWeek(-1), children: _jsx(ChevronLeft, { className: "size-4", "aria-hidden": true }) }), _jsx(Button, { size: "sm", variant: "secondary", onPress: () => setWeekAnchor(startOfWeek(new Date(), zone)), children: "Today" }), _jsx(Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Next week", onPress: () => shiftWeek(1), children: _jsx(ChevronRight, { className: "size-4", "aria-hidden": true }) }), _jsx("p", { className: "ml-2 text-sm font-medium", children: weekLabel(columns, zone) })] }), canBook && eventTypes.length > 0 && (_jsx(Button, { size: "sm", variant: "primary", onPress: () => setBooking({ startAt: nextHour(now) }), children: "New appointment" }))] }), error && _jsx("p", { className: "text-sm text-danger", children: error }), isLoading ? (_jsx(Skeleton, { className: "h-96 w-full rounded-lg" })) : (_jsxs(_Fragment, { children: [_jsx(Card, { className: "hidden overflow-hidden sm:block", children: _jsx(WeekGrid, { columns: columns, marks: marks, window: visibleWindow, positioned: positioned, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected, onPickEmpty: canBook ? (startAt) => setBooking({ startAt }) : undefined }) }), _jsx("div", { className: "sm:hidden", children: _jsx(AgendaView, { columns: columns, bookings: bookings, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected }) })] })), booking && (_jsx(ConnectBookingDialog, { isOpen: true, onOpenChange: (open) => !open && setBooking(null), eventTypes: eventTypes, initialStartAt: booking.startAt, timeZone: zone, onBooked: () => {
|
|
68
69
|
setBooking(null);
|
|
69
70
|
refetch();
|
|
70
71
|
} })), selected && (_jsx(ConnectBookingDetail, { booking: selected, eventType: eventTypeById.get(selected.eventTypeId) ?? null, timeZone: zone, onOpenChange: (open) => !open && setSelected(null), onChanged: () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Button } from "@heroui/react";
|
|
4
|
+
import { cn } from "../../cn.js";
|
|
4
5
|
/**
|
|
5
6
|
* Next / previous for a cursor-paginated list.
|
|
6
7
|
*
|
|
@@ -14,6 +15,6 @@ import { Button } from "@heroui/react";
|
|
|
14
15
|
* filtering or paging changed the result set. Without it the table silently
|
|
15
16
|
* swaps its contents.
|
|
16
17
|
*/
|
|
17
|
-
export function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending = false, noun = ["row", "rows"], }) {
|
|
18
|
-
return (_jsxs("div", { className: "flex items-center justify-between gap-3 pt-3", children: [_jsxs("p", { "aria-live": "polite", className: "text-xs text-muted", children: [shown, " ", shown === 1 ? noun[0] : noun[1]] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "secondary", isDisabled: !hasPrevious || isPending, onPress: onPrevious, children: "Previous" }), _jsx(Button, { size: "sm", variant: "secondary", isDisabled: nextCursor === null || isPending, onPress: () => nextCursor && onNext(nextCursor), children: "Next" })] })] }));
|
|
18
|
+
export function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending = false, noun = ["row", "rows"], className, }) {
|
|
19
|
+
return (_jsxs("div", { className: cn("flex items-center justify-between gap-3 pt-3", className), children: [_jsxs("p", { "aria-live": "polite", className: "text-xs text-muted", children: [shown, " ", shown === 1 ? noun[0] : noun[1]] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", variant: "secondary", isDisabled: !hasPrevious || isPending, onPress: onPrevious, children: "Previous" }), _jsx(Button, { size: "sm", variant: "secondary", isDisabled: nextCursor === null || isPending, onPress: () => nextCursor && onNext(nextCursor), children: "Next" })] })] }));
|
|
19
20
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Chip } from "@heroui/react";
|
|
4
|
+
import { cn } from "../../cn.js";
|
|
4
5
|
/**
|
|
5
6
|
* A single-select filter strip - the "All / Open / Won / Lost" row above a
|
|
6
7
|
* list.
|
|
@@ -10,12 +11,29 @@ import { Chip } from "@heroui/react";
|
|
|
10
11
|
* selected one should be announced as selected. A group of `<button>`s gets
|
|
11
12
|
* neither, and that is the usual hand-rolled shape.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Wraps rather than scrolling on a narrow viewport. See the note on the
|
|
15
|
+
* container below: a horizontal scroller nested in a vertically scrolling
|
|
16
|
+
* page hides its overflow rather than deferring it.
|
|
15
17
|
*/
|
|
16
|
-
export function ConnectFilterTabs({ options, value, onChange, label, }) {
|
|
17
|
-
return (
|
|
18
|
+
export function ConnectFilterTabs({ options, value, onChange, label, className, }) {
|
|
19
|
+
return (
|
|
20
|
+
/*
|
|
21
|
+
* Wraps. It does not scroll.
|
|
22
|
+
*
|
|
23
|
+
* It used to scroll horizontally, on the reasoning that a filter wrapping
|
|
24
|
+
* to three lines pushes the list itself off the screen. That trade was
|
|
25
|
+
* the wrong way round for a mobile-first product: a horizontal scroller
|
|
26
|
+
* inside a vertically scrolling page is a gesture people do not think to
|
|
27
|
+
* try, so the options past the fold were not "one swipe away" - they were
|
|
28
|
+
* invisible. The strip also came to rest mid-option, showing "ll" where
|
|
29
|
+
* "All" should be.
|
|
30
|
+
*
|
|
31
|
+
* Wrapping costs one extra line on a phone and nothing at all above it,
|
|
32
|
+
* where the row has always fitted. Every option is now on screen and
|
|
33
|
+
* reachable without a gesture, which is what a filter is for.
|
|
34
|
+
*/
|
|
35
|
+
_jsx("div", { role: "radiogroup", "aria-label": label, className: cn("-mx-1 flex flex-wrap gap-2 px-1", className), children: options.map((option) => {
|
|
18
36
|
const isSelected = option.value === value;
|
|
19
|
-
return (_jsx("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(option.value), className: "shrink-0 rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", children: _jsxs(Chip, {
|
|
37
|
+
return (_jsx("button", { type: "button", role: "radio", "aria-checked": isSelected, onClick: () => onChange(option.value), className: "shrink-0 rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", children: _jsxs(Chip, { variant: isSelected ? "primary" : "soft", color: isSelected ? "accent" : "default", className: "min-h-8 px-3.5 py-1.5 text-sm", children: [option.label, option.count !== undefined && _jsx("span", { className: "ml-1 opacity-70", children: option.count })] }) }, option.value ?? "__all"));
|
|
20
38
|
}) }));
|
|
21
39
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useConnectCompanyProfile } from "../provider.js";
|
|
4
|
+
import { cn } from "../../cn.js";
|
|
4
5
|
/**
|
|
5
6
|
* The DPDP s.13 grievance channel, rendered.
|
|
6
7
|
*
|
|
@@ -23,5 +24,5 @@ export function ConnectGrievanceNotice({ title = "Grievance redressal", applicat
|
|
|
23
24
|
if (!profile)
|
|
24
25
|
return null;
|
|
25
26
|
const { grievance } = profile;
|
|
26
|
-
return (_jsxs("section", { className:
|
|
27
|
+
return (_jsxs("section", { className: cn("space-y-3", className), children: [title !== null && _jsx("h3", { className: "text-base font-semibold", children: title }), _jsxs("p", { className: "text-sm text-muted", children: [applicationName ? `${applicationName} is operated on ` : "This service runs on ", "the CosmoOps Connect platform. ", _jsx("strong", { className: "font-medium text-foreground", children: profile.legalName }), " ", "is the Data Fiduciary for personal data processed here, under the Digital Personal Data Protection Act, 2023."] }), _jsxs("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-[auto_1fr]", children: [_jsx("dt", { className: "text-muted", children: "Grievance Officer" }), _jsx("dd", { className: "text-foreground", children: grievance.officerName }), _jsx("dt", { className: "text-muted", children: "Email" }), _jsx("dd", { children: _jsx("a", { className: "text-accent underline underline-offset-2", href: `mailto:${grievance.officerEmail}`, children: grievance.officerEmail }) }), _jsx("dt", { className: "text-muted", children: "Contactable" }), _jsx("dd", { className: "text-foreground", children: grievance.hours }), _jsx("dt", { className: "text-muted", children: "Registered address" }), _jsx("dd", { className: "text-foreground", children: profile.registeredAddress })] }), _jsxs("p", { className: "text-sm text-muted", children: ["Complaints are acknowledged and resolved within ", grievance.responseDays, " days of receipt."] })] }));
|
|
27
28
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ConnectMark } from "../mark.js";
|
|
4
|
+
import { cn } from "../../cn.js";
|
|
4
5
|
/**
|
|
5
6
|
* The shared platform lockup: an application's own glyph on the CosmoOps badge,
|
|
6
7
|
* `cosmo·ops` beside it, and the application name underneath.
|
|
@@ -19,7 +20,7 @@ export function ConnectLogo({ name, icon, variant, collapsed = false, className
|
|
|
19
20
|
const resolved = variant ?? (collapsed ? "mark" : "full");
|
|
20
21
|
const badge = (_jsxs("span", { className: "relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-[0.28em] bg-accent text-accent-foreground", style: { width: "2em", height: "2em" }, "aria-hidden": "true", children: [icon ? _jsx("span", { className: "inline-flex items-center justify-center [&>*]:size-[1.25em]", children: icon }) : null, !icon && _jsx(ConnectMark, { className: "absolute inset-0 size-full" })] }));
|
|
21
22
|
if (resolved === "mark") {
|
|
22
|
-
return (_jsx("span", { className:
|
|
23
|
+
return (_jsx("span", { className: cn("inline-flex text-sm leading-none", className), role: "img", "aria-label": name, children: badge }));
|
|
23
24
|
}
|
|
24
|
-
return (_jsxs("span", { className:
|
|
25
|
+
return (_jsxs("span", { className: cn("inline-flex min-w-0 items-center gap-[0.6em] text-sm leading-none", className), role: "img", "aria-label": `cosmoops ${name}`, children: [resolved === "full" && badge, _jsxs("span", { className: "flex min-w-0 flex-col leading-none", children: [_jsxs("span", { className: "inline-flex items-center font-extrabold tracking-tight text-foreground", "aria-hidden": "true", children: [_jsx("span", { children: "cosmo" }), _jsx("span", { className: "mx-[0.12em] inline-block size-[0.19em] shrink-0 rounded-[0.04em] bg-accent" }), _jsx("span", { children: "ops" })] }), _jsx("span", { className: "mt-[0.42em] truncate text-[0.6em] font-semibold uppercase tracking-[0.26em] text-accent", "aria-hidden": "true", children: name })] })] }));
|
|
25
26
|
}
|
|
@@ -8,6 +8,7 @@ import { ConnectMark } from "../mark.js";
|
|
|
8
8
|
import { initials } from "./identity.js";
|
|
9
9
|
import { ENTER, m, MotionProvider } from "./motion.js";
|
|
10
10
|
import { dismissOneTap, oneTapSuppressed } from "./one-tap-state.js";
|
|
11
|
+
import { cn } from "../../cn.js";
|
|
11
12
|
export function ConnectOneTap({ onInteractiveSignIn, className = "" }) {
|
|
12
13
|
const { session, isLoading, signIn, readOneTapHint, oneTapSignIn } = useConnect();
|
|
13
14
|
const [hint, setHint] = useState(null);
|
|
@@ -92,7 +93,7 @@ export function ConnectOneTap({ onInteractiveSignIn, className = "" }) {
|
|
|
92
93
|
// Fixed, above ordinary content but below a modal: this is an offer,
|
|
93
94
|
// and it must never sit on top of something the visitor opened
|
|
94
95
|
// themselves.
|
|
95
|
-
className:
|
|
96
|
+
className: cn("fixed inset-x-3 bottom-3 z-40 sm:inset-x-auto sm:right-4 sm:bottom-4 sm:w-80", className), onKeyDown: (event) => {
|
|
96
97
|
if (event.key === "Escape")
|
|
97
98
|
dismiss();
|
|
98
99
|
}, children: _jsxs(Card, { className: "p-4 shadow-lg", children: [_jsxs("div", { className: "flex items-start gap-3", children: [hint.consented ? (_jsxs(Avatar, { size: "md", color: "accent", variant: "soft", className: "shrink-0 rounded-full", children: [hint.user?.photoUrl && (_jsx(Avatar.Image, { alt: displayName ?? "Your account", src: hint.user.photoUrl })), _jsx(Avatar.Fallback, { children: initials(hint.user?.displayName ?? null, hint.user?.email ?? null) })] })) : (_jsx(ConnectMark, { className: "mt-0.5 size-8 shrink-0" })), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium text-foreground", children: hint.consented ? `Continue as ${displayName ?? "your account"}` : "You're signed in to CosmoOps" }), _jsx("p", { className: "truncate text-xs text-muted", children: hint.consented
|
|
@@ -8,6 +8,7 @@ import { buildCancelUrl, buildCheckoutUrl, useConnectCheckout, } from "../use-ch
|
|
|
8
8
|
import { AnimatePresence, ENTER, m, MotionProvider } from "./motion.js";
|
|
9
9
|
import { ConnectSubscribeDialog } from "./subscribe-dialog.js";
|
|
10
10
|
import { ACTION_LABEL, checkoutSubscriptionId, discountLabel, formatPrice, groupPlansByTier, intervalLabel, isFreePlan, pricePerLabel, resolveAction, } from "./plan-pricing.js";
|
|
11
|
+
import { cn } from "../../cn.js";
|
|
11
12
|
/**
|
|
12
13
|
* The plan table: monthly/annual toggle, features, add-ons, the discount
|
|
13
14
|
* actually in effect, and the subscribe / upgrade / downgrade flows.
|
|
@@ -102,7 +103,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
102
103
|
setPending(null);
|
|
103
104
|
}
|
|
104
105
|
if (plansLoading || entitlementsLoading) {
|
|
105
|
-
return (_jsx("div", { className:
|
|
106
|
+
return (_jsx("div", { className: cn("grid gap-4 sm:grid-cols-2 lg:grid-cols-3", className), children: Array.from({ length: 3 }, (_, index) => (_jsx(Skeleton, { className: "h-80 rounded-xl" }, index))) }));
|
|
106
107
|
}
|
|
107
108
|
if (!session?.user) {
|
|
108
109
|
return _jsx("p", { className: "text-sm text-muted", children: "Sign in to see your plan options." });
|
|
@@ -110,7 +111,7 @@ export function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
110
111
|
if (error) {
|
|
111
112
|
return _jsx("p", { className: "text-sm text-danger", children: "Could not load plans." });
|
|
112
113
|
}
|
|
113
|
-
return (_jsx(MotionProvider, { children: _jsxs("div", { className:
|
|
114
|
+
return (_jsx(MotionProvider, { children: _jsxs("div", { className: cn("flex flex-col gap-6", className), children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), _jsx(Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: _jsx(Switch.Content, { children: _jsx(Switch.Control, { children: _jsx(Switch.Thumb, {}) }) }) }), _jsx("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && (_jsxs(Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), _jsx(Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [_jsx(Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), _jsxs("span", { children: [discountLabel(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
|
|
114
115
|
? ` - code ${subscription.subscription.appliedDiscount.code}`
|
|
115
116
|
: ""] })] })), _jsx(ConnectCheckoutStatusRow, { checkout: checkout }), _jsx(m.div, { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", variants: { show: { transition: { staggerChildren: 0.06 } } }, initial: "hidden", animate: "show", children: tiers.map((tier) => {
|
|
116
117
|
// The price point this card is showing. A tier missing the toggled
|
|
@@ -5,6 +5,7 @@ import { AlertTriangle, ExternalLink, Info, Loader2 } from "lucide-react";
|
|
|
5
5
|
import { blockingBlockers } from "../../helpers.js";
|
|
6
6
|
import { useConnect, useConnectEntitlements } from "../provider.js";
|
|
7
7
|
import { ENTER, m, MotionProvider } from "./motion.js";
|
|
8
|
+
import { cn } from "../../cn.js";
|
|
8
9
|
/**
|
|
9
10
|
* Codes that mean "somebody at Connect is looking at it" - there is no action
|
|
10
11
|
* for the customer to take, so these get an informational treatment and no
|
|
@@ -57,14 +58,14 @@ export function ConnectReadinessBanner({ includeAdvisory = false, hideWhenReady
|
|
|
57
58
|
if (!session?.user)
|
|
58
59
|
return null;
|
|
59
60
|
if (isLoading) {
|
|
60
|
-
return (_jsxs("div", { className:
|
|
61
|
+
return (_jsxs("div", { className: cn("flex items-center gap-2 text-sm text-muted", className), children: [_jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), "Checking your workspace\u2026"] }));
|
|
61
62
|
}
|
|
62
63
|
if (!data)
|
|
63
64
|
return null;
|
|
64
65
|
const blockers = includeAdvisory ? data.readiness.readiness.blockers : blockingBlockers(data.readiness);
|
|
65
66
|
if (blockers.length === 0)
|
|
66
67
|
return hideWhenReady ? null : _jsx(ReadyRow, { className: className });
|
|
67
|
-
return (_jsx(MotionProvider, { children: _jsx(m.div, { className:
|
|
68
|
+
return (_jsx(MotionProvider, { children: _jsx(m.div, { className: cn("flex flex-col gap-2", className), role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: ENTER, children: blockers.map((blocker) => (_jsx(BlockerRow, { blocker: blocker }, `${blocker.code}-${blocker.message}`))) }) }));
|
|
68
69
|
}
|
|
69
70
|
function BlockerRow({ blocker }) {
|
|
70
71
|
const inProgress = IN_PROGRESS_CODES.has(blocker.code);
|
|
@@ -72,7 +73,7 @@ function BlockerRow({ blocker }) {
|
|
|
72
73
|
return (_jsxs("div", { className: `flex flex-wrap items-center justify-between gap-3 rounded-lg px-3 py-2 text-sm ${inProgress || !isBlocking ? "bg-surface text-muted" : "bg-danger-soft text-danger"}`, children: [_jsxs("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? (_jsx(Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : (_jsx(AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && (_jsxs(Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: [actionLabel(blocker), _jsx(ExternalLink, { className: "size-3.5" })] }))] }));
|
|
73
74
|
}
|
|
74
75
|
function ReadyRow({ className }) {
|
|
75
|
-
return (_jsx("p", { className:
|
|
76
|
+
return (_jsx("p", { className: cn("text-sm text-muted", className), children: "This workspace is cleared to use every part of the application." }));
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
79
|
* Renders `children` only once the workspace clears every blocking gate.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, use } from "react";
|
|
4
4
|
import { m } from "./motion.js";
|
|
5
|
+
import { cn } from "../../cn.js";
|
|
5
6
|
/** A plain anchor - correct anywhere, and a full page load. See `ConnectLinkRenderer`. */
|
|
6
7
|
export const defaultRenderLink = ({ children, ...props }) => _jsx("a", { ...props, children: children });
|
|
7
8
|
/**
|
|
@@ -137,5 +138,5 @@ export function useConnectShell() {
|
|
|
137
138
|
*/
|
|
138
139
|
export function SidebarLabel({ children, className, }) {
|
|
139
140
|
const { collapsed } = useConnectShell();
|
|
140
|
-
return (_jsx(m.span, { className:
|
|
141
|
+
return (_jsx(m.span, { className: cn("min-w-0 flex-1 truncate text-start", className), animate: { opacity: collapsed ? 0 : 1 }, transition: collapsed ? { duration: 0.1 } : { duration: 0.18, delay: 0.1 }, "aria-hidden": collapsed, children: children }));
|
|
141
142
|
}
|
|
@@ -5,6 +5,7 @@ import { ArrowUpRight } from "lucide-react";
|
|
|
5
5
|
import { m } from "./motion.js";
|
|
6
6
|
import { SidebarLabel, defaultRenderLink, sidebarRowClass, useConnectShell } from "./shell-context.js";
|
|
7
7
|
import { useActivePath } from "../use-active-path.js";
|
|
8
|
+
import { cn } from "../../cn.js";
|
|
8
9
|
import { isNavItemActive, normalizeNav, resolveActivePath, } from "./nav.js";
|
|
9
10
|
export { defaultRenderLink };
|
|
10
11
|
function rowClass(isActive, depth) {
|
|
@@ -64,7 +65,7 @@ export function ConnectSidebarNav({ nav, activePath, renderLink, collapsed, onNa
|
|
|
64
65
|
const pathname = useActivePath(activePath ?? shell.activePath);
|
|
65
66
|
const active = resolveActivePath(pathname, nav);
|
|
66
67
|
const sections = normalizeNav(nav);
|
|
67
|
-
return (_jsx("div", { className:
|
|
68
|
+
return (_jsx("div", { className: cn("flex flex-col gap-2", className), children: sections.map((section, index) => (_jsxs("nav", { "aria-label": section.title ?? "Main", className: "flex flex-col gap-1", children: [section.title && _jsx(SectionHeading, { title: section.title, collapsed: isCollapsed }), section.items.map((item) => {
|
|
68
69
|
const isActive = isNavItemActive(item, active);
|
|
69
70
|
const activeChild = (item.items ?? []).some((child) => isNavItemActive(child, active));
|
|
70
71
|
return (_jsxs("div", { className: "flex w-full flex-col gap-1", children: [_jsx(NavRow, { item: item, isActive: isActive, hasActiveChild: activeChild, collapsed: isCollapsed, depth: 0, renderLink: link, onNavigate: navigate }), !isCollapsed &&
|