@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/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,78 @@
|
|
|
3
3
|
`@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
|
|
4
4
|
versions may carry breaking changes** and patch versions never do.
|
|
5
5
|
|
|
6
|
+
## 0.26.0
|
|
7
|
+
|
|
8
|
+
A minor rather than a patch because two of these change how existing screens
|
|
9
|
+
*look*, without changing any API. 0.25.1 was never published; its notes below
|
|
10
|
+
ship as part of this release.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **The shared theme now sets `--radius`, and partner platforms get smaller
|
|
15
|
+
corners.** It never carried the token, so every platform importing it fell
|
|
16
|
+
through to HeroUI's stock `--radius: 0.5rem` with `--field-radius` derived
|
|
17
|
+
at 1.5x - while Connect's own app has always run at `0.25rem`. Cards,
|
|
18
|
+
inputs, chips and buttons in a partner app were drawn at twice the radius of
|
|
19
|
+
the same component in Connect, which was the one place two products sharing
|
|
20
|
+
this palette visibly disagreed. HeroUI's whole radius scale is *ratios* of
|
|
21
|
+
that one token, so this is a single value and every corner follows it.
|
|
22
|
+
To keep the old look, redefine both after the import:
|
|
23
|
+
`:root { --radius: 0.5rem; --field-radius: calc(var(--radius) * 1.5); }`.
|
|
24
|
+
|
|
25
|
+
- **`<ConnectAppShell>`'s content area is full width.** It was capped at
|
|
26
|
+
`max-w-6xl` and centred, which spent the width twice: dead margin down both
|
|
27
|
+
sides, and a fitted layout - a board that folds a column away when it runs
|
|
28
|
+
out of room - holding the pixels that would have paid for another column.
|
|
29
|
+
Landing surfaces on an application platform are boards, tables and
|
|
30
|
+
dashboards, and all three want the monitor. Forms and prose should cap
|
|
31
|
+
themselves at the surface rather than have the shell cap everything; pass
|
|
32
|
+
`contentClassName` to restore a frame of your own.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **`className` now actually overrides.** Every component concatenated your
|
|
37
|
+
classes onto its defaults - `` `flex flex-col gap-4 ${className}` `` - which
|
|
38
|
+
emits *both* rules, leaving the winner to Tailwind's output order rather
|
|
39
|
+
than to you. So an override worked on one prop and silently did nothing on
|
|
40
|
+
the next, with no way to tell which you were getting. All 27 sites now merge
|
|
41
|
+
through `tailwind-merge`, which *removes* the conflicting default:
|
|
42
|
+
`gap-8` beats `gap-4`, and `p-8` beats `px-3 py-2`.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **`cn` is exported from `/ui`** - the same merge the components use, for
|
|
47
|
+
composing your own on top of them.
|
|
48
|
+
|
|
49
|
+
- **`className` on `<ConnectPageShell>`, `<ConnectCursorPagination>` and
|
|
50
|
+
`<ConnectFilterTabs>`**, which had no way to be styled at all. Components
|
|
51
|
+
that render only `children` (`<ConnectPlatformGate>`,
|
|
52
|
+
`<ConnectWorkspaceTypeGate>`, `<MotionProvider>`) still take none, because
|
|
53
|
+
they have no element to put it on.
|
|
54
|
+
|
|
55
|
+
## 0.25.1
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **`<ConnectFilterTabs>` chips had almost no padding.** They were drawn at
|
|
60
|
+
`size="sm"`, which in `@heroui/styles` is not a smaller chip but
|
|
61
|
+
`px-1 py-0` - four pixels of horizontal padding and none at all vertically.
|
|
62
|
+
That is reasonable on a label chip and wrong on the primary control of a
|
|
63
|
+
list: the filter strip read as a row of bare words, and the press target
|
|
64
|
+
was barely taller than its own text on a control people hit constantly,
|
|
65
|
+
often with a thumb. Now explicit `px-3.5 py-1.5` with a `min-h-8`, so a
|
|
66
|
+
chip carrying a count and one without still share a shape.
|
|
67
|
+
|
|
68
|
+
- **A hand-rolled `<button>` showed an arrow cursor, not a pointer.**
|
|
69
|
+
Tailwind v4's Preflight sets `cursor: default` on `button`, reversing v3,
|
|
70
|
+
and HeroUI restores the pointer only on its own classes via
|
|
71
|
+
`--cursor-interactive`. So every plain `<button>` an app writes - a menu
|
|
72
|
+
row, a filter chip, a calendar day, a card that is really a press target -
|
|
73
|
+
rendered as though it were text. `theme.css` now corrects it once, at zero
|
|
74
|
+
specificity via `:where()`, so component rules including HeroUI's own
|
|
75
|
+
disabled handling still win. Every application platform importing the theme
|
|
76
|
+
picks it up.
|
|
77
|
+
|
|
6
78
|
## 0.25.0
|
|
7
79
|
|
|
8
80
|
### Changed
|
|
@@ -11,6 +11,7 @@ const helpers_js_1 = require("../../helpers.js");
|
|
|
11
11
|
const provider_js_1 = require("../provider.js");
|
|
12
12
|
const motion_js_1 = require("./motion.js");
|
|
13
13
|
const readiness_js_1 = require("./readiness.js");
|
|
14
|
+
const cn_js_1 = require("../../cn.js");
|
|
14
15
|
function formatDate(iso) {
|
|
15
16
|
return new Date(iso).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric" });
|
|
16
17
|
}
|
|
@@ -121,15 +122,40 @@ function useConnectAccessGate() {
|
|
|
121
122
|
* entire app must not hide a public landing page behind an empty gate. Your
|
|
122
123
|
* own sign-in flow decides what a signed-out visitor sees.
|
|
123
124
|
*
|
|
125
|
+
* **While the verdict is unknown, `children` render.** The two reads behind
|
|
126
|
+
* this gate are client-side round trips that happen on *every* page load, and
|
|
127
|
+
* this component is mounted around an entire application - so blocking on
|
|
128
|
+
* them replaced every partner's whole page with one placeholder until both
|
|
129
|
+
* came back. Anything the host had streamed underneath was discarded: per-
|
|
130
|
+
* section skeletons never painted, and a page built as a static shell with
|
|
131
|
+
* suspended regions arrived all at once anyway. That is the common path, paid
|
|
132
|
+
* by every workspace on every navigation.
|
|
133
|
+
*
|
|
134
|
+
* Rendering `children` instead makes the loading case agree with the two
|
|
135
|
+
* cases either side of it. A *failed* read already falls through to `clear`
|
|
136
|
+
* ("fail open rather than hard-block the whole app on a network hiccup", in
|
|
137
|
+
* `useConnectAccessGate`), and a signed-out visitor already renders
|
|
138
|
+
* `children` too - "we do not know yet" was the one unknown treated as a
|
|
139
|
+
* refusal.
|
|
140
|
+
*
|
|
141
|
+
* What it costs is honest and small: a genuinely blocked workspace sees its
|
|
142
|
+
* app for the length of one round trip before the block card replaces it.
|
|
143
|
+
* That is the rare path, it is self-correcting, and it is the same exposure
|
|
144
|
+
* the failed-read branch has always accepted. Pass `loading` to opt back into
|
|
145
|
+
* a placeholder for a gate around one small region.
|
|
146
|
+
*
|
|
124
147
|
* **This is a UX gate, not a security boundary** - same caveat as every
|
|
125
|
-
* other gate in this package
|
|
126
|
-
* `client.entitlements.getReadiness` on your
|
|
127
|
-
* thing.
|
|
148
|
+
* other gate in this package, and the reason the trade above is available at
|
|
149
|
+
* all. The authoritative check is `client.entitlements.getReadiness` on your
|
|
150
|
+
* server, before you do the thing.
|
|
128
151
|
*/
|
|
129
152
|
function ConnectAccessGate({ children, fallback, loading, className }) {
|
|
130
153
|
const gate = useConnectAccessGate();
|
|
154
|
+
// `children`, unless the host explicitly asked for a placeholder. See the
|
|
155
|
+
// doc comment - this is the branch that decides whether a partner's page
|
|
156
|
+
// can stream.
|
|
131
157
|
if (gate.state === "loading") {
|
|
132
|
-
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading ??
|
|
158
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading ?? children });
|
|
133
159
|
}
|
|
134
160
|
if (gate.state === "signed_out" || gate.state === "clear") {
|
|
135
161
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
@@ -208,7 +234,7 @@ function PaymentRetryBanner({ blocker, className, }) {
|
|
|
208
234
|
const [dismissed, setDismissed] = (0, react_1.useState)(() => dismissKey !== null && typeof window !== "undefined" && window.localStorage.getItem(dismissKey) === "1");
|
|
209
235
|
if (dismissed)
|
|
210
236
|
return null;
|
|
211
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)(motion_js_1.m.div, { className:
|
|
237
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)(motion_js_1.m.div, { className: (0, cn_js_1.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: motion_js_1.ENTER, children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" }), blocker.message, blocker.graceEndsAt && ` Resolve by ${formatDate(blocker.graceEndsAt)} to avoid losing access.`] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [blocker.resolveUrl && ((0, jsx_runtime_1.jsxs)(react_2.Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: ["Resolve payment", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] })), (0, jsx_runtime_1.jsx)(react_2.Button, { variant: "tertiary", isIconOnly: true, "aria-label": "Dismiss", onPress: () => {
|
|
212
238
|
setDismissed(true);
|
|
213
239
|
if (dismissKey && typeof window !== "undefined")
|
|
214
240
|
window.localStorage.setItem(dismissKey, "1");
|
|
@@ -10,6 +10,7 @@ const provider_js_1 = require("../provider.js");
|
|
|
10
10
|
const motion_js_1 = require("./motion.js");
|
|
11
11
|
const surfaces_js_1 = require("./surfaces.js");
|
|
12
12
|
const format_relative_time_js_1 = require("../../format-relative-time.js");
|
|
13
|
+
const cn_js_1 = require("../../cn.js");
|
|
13
14
|
const DEVICE_ICONS = {
|
|
14
15
|
desktop: lucide_react_1.Monitor,
|
|
15
16
|
mobile: lucide_react_1.Smartphone,
|
|
@@ -80,13 +81,13 @@ function ConnectAccountSessions({ className, maxVisible = 4 }) {
|
|
|
80
81
|
const [isRevokingOthers, setIsRevokingOthers] = (0, react_1.useState)(false);
|
|
81
82
|
const [showAll, setShowAll] = (0, react_1.useState)(false);
|
|
82
83
|
if (isLoading) {
|
|
83
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className:
|
|
84
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("flex flex-col gap-3", className), children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-4 w-28" }), (0, jsx_runtime_1.jsx)("div", { className: `flex flex-col divide-y divide-border overflow-hidden rounded-xl border border-border ${surfaces_js_1.PANEL_FILL}`, children: Array.from({ length: 2 }, (_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 px-3 py-3", children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "size-9 shrink-0 rounded-xl" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-3.5 w-32" }), (0, jsx_runtime_1.jsx)(react_2.Skeleton, { className: "h-3 w-24" })] })] }, index))) })] }));
|
|
84
85
|
}
|
|
85
86
|
// A failed read used to render as *nothing*, which is indistinguishable
|
|
86
87
|
// from "you have no other devices" - the one reading of this list that
|
|
87
88
|
// should never be guessed at.
|
|
88
89
|
if (error) {
|
|
89
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className:
|
|
90
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("flex flex-col gap-2", className), children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Active sessions" }), (0, jsx_runtime_1.jsx)("p", { className: `rounded-xl border border-border ${surfaces_js_1.PANEL_FILL} px-3 py-4 text-xs text-muted`, children: "Could not load your sessions. Try again in a moment." })] }));
|
|
90
91
|
}
|
|
91
92
|
const sessions = ordered(data?.items ?? []);
|
|
92
93
|
if (sessions.length === 0)
|
|
@@ -94,7 +95,7 @@ function ConnectAccountSessions({ className, maxVisible = 4 }) {
|
|
|
94
95
|
const hasOtherSessions = sessions.some((session) => !session.isCurrent);
|
|
95
96
|
const isCollapsible = maxVisible > 0 && sessions.length > maxVisible;
|
|
96
97
|
const visible = isCollapsible && !showAll ? sessions.slice(0, maxVisible) : sessions;
|
|
97
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("section", { className:
|
|
98
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("flex flex-col gap-3", className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "min-w-0", children: [(0, jsx_runtime_1.jsxs)("p", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: ["Active sessions", (0, jsx_runtime_1.jsx)(react_2.Chip, { size: "sm", variant: "soft", children: sessions.length })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-muted", children: "Devices signed in to your Connect account." })] }), hasOtherSessions && ((0, jsx_runtime_1.jsx)(react_2.Button, { size: "sm", variant: "tertiary", className: "shrink-0 text-xs text-danger", isPending: isRevokingOthers, isDisabled: isRevokingOthers, onPress: () => {
|
|
98
99
|
setIsRevokingOthers(true);
|
|
99
100
|
void mutateResource("/sessions/revoke-others")
|
|
100
101
|
.then(() => {
|
|
@@ -20,6 +20,7 @@ const workspace_switcher_js_1 = require("./workspace-switcher.js");
|
|
|
20
20
|
const user_menu_js_1 = require("./user-menu.js");
|
|
21
21
|
const nav_js_1 = require("./nav.js");
|
|
22
22
|
const use_active_path_js_1 = require("../use-active-path.js");
|
|
23
|
+
const cn_js_1 = require("../../cn.js");
|
|
23
24
|
const COLLAPSED_KEY = "connect_sidenav_collapsed";
|
|
24
25
|
const COLLAPSED_EVENT = "connect:sidenav-collapsed";
|
|
25
26
|
/**
|
|
@@ -188,5 +189,5 @@ function AppShell({ name, icon, homeHref = "/", nav, secondaryNav, activePath, r
|
|
|
188
189
|
}), [renderLink, activePath, closeMobile]);
|
|
189
190
|
const accountRow = signedOut ? (showAuthButtons && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)(auth_buttons_js_1.ConnectLoginButton, { size: "sm", fullWidth: true }), (0, jsx_runtime_1.jsx)(auth_buttons_js_1.ConnectRegisterButton, { size: "sm", fullWidth: true })] }))) : ((0, jsx_runtime_1.jsx)(user_menu_js_1.ConnectUserButton, { variant: "row", theme: theme, onThemeChange: onThemeChange }));
|
|
190
191
|
const railContent = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebarHeader, { name: name, icon: icon, homeHref: homeHref }), workspaceSwitcher !== false && ((0, jsx_runtime_1.jsx)("div", { className: "shrink-0 px-3 pb-1", children: (0, jsx_runtime_1.jsx)(workspace_switcher_js_1.ConnectWorkspaceSwitcher, {}) })), (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebarBody, { children: (0, jsx_runtime_1.jsx)(side_nav_js_1.ConnectSidebarNav, { nav: visibleNav }) }), (visibleSecondaryNav || sidebarFooter || showAppsGrid) && ((0, jsx_runtime_1.jsxs)(sidebar_js_1.ConnectSidebarFooter, { separated: false, children: [visibleSecondaryNav && (0, jsx_runtime_1.jsx)(side_nav_js_1.ConnectSidebarNav, { nav: visibleSecondaryNav }), sidebarFooter, showAppsGrid && (0, jsx_runtime_1.jsx)(apps_grid_js_1.ConnectAppsGrid, { variant: "row" })] })), accountRow && (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebarFooter, { children: accountRow })] }));
|
|
191
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className:
|
|
192
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex h-screen bg-background", className), children: [(0, jsx_runtime_1.jsx)(shell_context_js_1.ConnectShellProvider, { value: railShell, children: (0, jsx_runtime_1.jsx)(sidebar_js_1.ConnectSidebar, { children: railContent }) }), (0, jsx_runtime_1.jsx)(react_2.Drawer.Backdrop, { isOpen: mobileOpen, onOpenChange: setMobileOpen, children: (0, jsx_runtime_1.jsx)(react_2.Drawer.Content, { placement: "left", className: "w-[min(20rem,85vw)]", children: (0, jsx_runtime_1.jsx)(react_2.Drawer.Dialog, { "aria-label": "Navigation menu", className: "p-0", children: (0, jsx_runtime_1.jsx)(shell_context_js_1.ConnectShellProvider, { value: drawerShell, children: (0, jsx_runtime_1.jsx)("div", { className: "flex h-full flex-col", children: railContent }) }) }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col", children: [(0, jsx_runtime_1.jsxs)("header", { className: "flex h-14 shrink-0 items-center gap-2 border-b border-border bg-surface px-3 md:hidden", children: [(0, jsx_runtime_1.jsx)(react_2.Button, { isIconOnly: true, variant: "tertiary", size: "sm", "aria-label": "Open navigation menu", onPress: () => setMobileOpen(true), children: (0, jsx_runtime_1.jsx)(lucide_react_1.Menu, { className: "size-5" }) }), (0, jsx_runtime_1.jsx)(logo_js_1.ConnectLogo, { name: name, icon: icon })] }), (0, jsx_runtime_1.jsx)("main", { className: "flex-1 overflow-y-auto", children: (0, jsx_runtime_1.jsxs)("div", { className: contentClassName ?? "w-full px-4 py-5 md:px-6 md:py-6", children: [gated ? (0, jsx_runtime_1.jsx)(access_gate_js_1.ConnectAccessGate, { children: children }) : children, footer] }) })] }), askAi && (0, jsx_runtime_1.jsx)(ask_ai_js_1.ConnectAskAi, { ...(askAi === true ? {} : askAi) })] }) }));
|
|
192
193
|
}
|
|
@@ -11,6 +11,7 @@ const empty_state_js_1 = require("../../ui/empty-state.js");
|
|
|
11
11
|
const use_scheduling_js_1 = require("../use-scheduling.js");
|
|
12
12
|
const booking_grid_js_1 = require("./booking-grid.js");
|
|
13
13
|
const booking_dialogs_js_1 = require("./booking-dialogs.js");
|
|
14
|
+
const cn_js_1 = require("../../cn.js");
|
|
14
15
|
/**
|
|
15
16
|
* The visible band of the day. Outside it a booking is drawn at the edge and
|
|
16
17
|
* flagged rather than hidden - see `layoutBookings`.
|
|
@@ -68,7 +69,7 @@ function ConnectBookingCalendar({ timeZone, hostMemberId, window: visibleWindow
|
|
|
68
69
|
const marks = (0, react_2.useMemo)(() => (0, booking_grid_js_1.hourMarks)(visibleWindow), [visibleWindow]);
|
|
69
70
|
const shiftWeek = (weeks) => setWeekAnchor((anchor) => new Date(anchor.getTime() + weeks * 7 * 24 * 60 * 60_000));
|
|
70
71
|
const eventTypeById = (0, react_2.useMemo)(() => new Map(eventTypes.map((eventType) => [eventType.id, eventType])), [eventTypes]);
|
|
71
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className:
|
|
72
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex flex-col gap-4", className), children: [(0, jsx_runtime_1.jsxs)("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Previous week", onPress: () => shiftWeek(-1), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { className: "size-4", "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", onPress: () => setWeekAnchor(startOfWeek(new Date(), zone)), children: "Today" }), (0, jsx_runtime_1.jsx)(react_1.Button, { isIconOnly: true, size: "sm", variant: "tertiary", "aria-label": "Next week", onPress: () => shiftWeek(1), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "size-4", "aria-hidden": true }) }), (0, jsx_runtime_1.jsx)("p", { className: "ml-2 text-sm font-medium", children: weekLabel(columns, zone) })] }), canBook && eventTypes.length > 0 && ((0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "primary", onPress: () => setBooking({ startAt: nextHour(now) }), children: "New appointment" }))] }), error && (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: error }), isLoading ? ((0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-96 w-full rounded-lg" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.Card, { className: "hidden overflow-hidden sm:block", children: (0, jsx_runtime_1.jsx)(WeekGrid, { columns: columns, marks: marks, window: visibleWindow, positioned: positioned, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected, onPickEmpty: canBook ? (startAt) => setBooking({ startAt }) : undefined }) }), (0, jsx_runtime_1.jsx)("div", { className: "sm:hidden", children: (0, jsx_runtime_1.jsx)(AgendaView, { columns: columns, bookings: bookings, eventTypeById: eventTypeById, zone: zone, onSelect: setSelected }) })] })), booking && ((0, jsx_runtime_1.jsx)(booking_dialogs_js_1.ConnectBookingDialog, { isOpen: true, onOpenChange: (open) => !open && setBooking(null), eventTypes: eventTypes, initialStartAt: booking.startAt, timeZone: zone, onBooked: () => {
|
|
72
73
|
setBooking(null);
|
|
73
74
|
refetch();
|
|
74
75
|
} })), selected && ((0, jsx_runtime_1.jsx)(booking_dialogs_js_1.ConnectBookingDetail, { booking: selected, eventType: eventTypeById.get(selected.eventTypeId) ?? null, timeZone: zone, onOpenChange: (open) => !open && setSelected(null), onChanged: () => {
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.ConnectCursorPagination = ConnectCursorPagination;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const react_1 = require("@heroui/react");
|
|
7
|
+
const cn_js_1 = require("../../cn.js");
|
|
7
8
|
/**
|
|
8
9
|
* Next / previous for a cursor-paginated list.
|
|
9
10
|
*
|
|
@@ -17,6 +18,6 @@ const react_1 = require("@heroui/react");
|
|
|
17
18
|
* filtering or paging changed the result set. Without it the table silently
|
|
18
19
|
* swaps its contents.
|
|
19
20
|
*/
|
|
20
|
-
function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending = false, noun = ["row", "rows"], }) {
|
|
21
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-3 pt-3", children: [(0, jsx_runtime_1.jsxs)("p", { "aria-live": "polite", className: "text-xs text-muted", children: [shown, " ", shown === 1 ? noun[0] : noun[1]] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: !hasPrevious || isPending, onPress: onPrevious, children: "Previous" }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: nextCursor === null || isPending, onPress: () => nextCursor && onNext(nextCursor), children: "Next" })] })] }));
|
|
21
|
+
function ConnectCursorPagination({ shown, nextCursor, hasPrevious, onNext, onPrevious, isPending = false, noun = ["row", "rows"], className, }) {
|
|
22
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex items-center justify-between gap-3 pt-3", className), children: [(0, jsx_runtime_1.jsxs)("p", { "aria-live": "polite", className: "text-xs text-muted", children: [shown, " ", shown === 1 ? noun[0] : noun[1]] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: !hasPrevious || isPending, onPress: onPrevious, children: "Previous" }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "secondary", isDisabled: nextCursor === null || isPending, onPress: () => nextCursor && onNext(nextCursor), children: "Next" })] })] }));
|
|
22
23
|
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.ConnectFilterTabs = ConnectFilterTabs;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const react_1 = require("@heroui/react");
|
|
7
|
+
const cn_js_1 = require("../../cn.js");
|
|
7
8
|
/**
|
|
8
9
|
* A single-select filter strip - the "All / Open / Won / Lost" row above a
|
|
9
10
|
* list.
|
|
@@ -13,12 +14,29 @@ const react_1 = require("@heroui/react");
|
|
|
13
14
|
* selected one should be announced as selected. A group of `<button>`s gets
|
|
14
15
|
* neither, and that is the usual hand-rolled shape.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Wraps rather than scrolling on a narrow viewport. See the note on the
|
|
18
|
+
* container below: a horizontal scroller nested in a vertically scrolling
|
|
19
|
+
* page hides its overflow rather than deferring it.
|
|
18
20
|
*/
|
|
19
|
-
function ConnectFilterTabs({ options, value, onChange, label, }) {
|
|
20
|
-
return (
|
|
21
|
+
function ConnectFilterTabs({ options, value, onChange, label, className, }) {
|
|
22
|
+
return (
|
|
23
|
+
/*
|
|
24
|
+
* Wraps. It does not scroll.
|
|
25
|
+
*
|
|
26
|
+
* It used to scroll horizontally, on the reasoning that a filter wrapping
|
|
27
|
+
* to three lines pushes the list itself off the screen. That trade was
|
|
28
|
+
* the wrong way round for a mobile-first product: a horizontal scroller
|
|
29
|
+
* inside a vertically scrolling page is a gesture people do not think to
|
|
30
|
+
* try, so the options past the fold were not "one swipe away" - they were
|
|
31
|
+
* invisible. The strip also came to rest mid-option, showing "ll" where
|
|
32
|
+
* "All" should be.
|
|
33
|
+
*
|
|
34
|
+
* Wrapping costs one extra line on a phone and nothing at all above it,
|
|
35
|
+
* where the row has always fitted. Every option is now on screen and
|
|
36
|
+
* reachable without a gesture, which is what a filter is for.
|
|
37
|
+
*/
|
|
38
|
+
(0, jsx_runtime_1.jsx)("div", { role: "radiogroup", "aria-label": label, className: (0, cn_js_1.cn)("-mx-1 flex flex-wrap gap-2 px-1", className), children: options.map((option) => {
|
|
21
39
|
const isSelected = option.value === value;
|
|
22
|
-
return ((0, jsx_runtime_1.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: (0, jsx_runtime_1.jsxs)(react_1.Chip, {
|
|
40
|
+
return ((0, jsx_runtime_1.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: (0, jsx_runtime_1.jsxs)(react_1.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 && (0, jsx_runtime_1.jsx)("span", { className: "ml-1 opacity-70", children: option.count })] }) }, option.value ?? "__all"));
|
|
23
41
|
}) }));
|
|
24
42
|
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.ConnectGrievanceNotice = ConnectGrievanceNotice;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const provider_js_1 = require("../provider.js");
|
|
7
|
+
const cn_js_1 = require("../../cn.js");
|
|
7
8
|
/**
|
|
8
9
|
* The DPDP s.13 grievance channel, rendered.
|
|
9
10
|
*
|
|
@@ -26,5 +27,5 @@ function ConnectGrievanceNotice({ title = "Grievance redressal", applicationName
|
|
|
26
27
|
if (!profile)
|
|
27
28
|
return null;
|
|
28
29
|
const { grievance } = profile;
|
|
29
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className:
|
|
30
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: (0, cn_js_1.cn)("space-y-3", className), children: [title !== null && (0, jsx_runtime_1.jsx)("h3", { className: "text-base font-semibold", children: title }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: [applicationName ? `${applicationName} is operated on ` : "This service runs on ", "the CosmoOps Connect platform. ", (0, jsx_runtime_1.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."] }), (0, jsx_runtime_1.jsxs)("dl", { className: "grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-[auto_1fr]", children: [(0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Grievance Officer" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: grievance.officerName }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Email" }), (0, jsx_runtime_1.jsx)("dd", { children: (0, jsx_runtime_1.jsx)("a", { className: "text-accent underline underline-offset-2", href: `mailto:${grievance.officerEmail}`, children: grievance.officerEmail }) }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Contactable" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: grievance.hours }), (0, jsx_runtime_1.jsx)("dt", { className: "text-muted", children: "Registered address" }), (0, jsx_runtime_1.jsx)("dd", { className: "text-foreground", children: profile.registeredAddress })] }), (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-muted", children: ["Complaints are acknowledged and resolved within ", grievance.responseDays, " days of receipt."] })] }));
|
|
30
31
|
}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.ConnectLogo = ConnectLogo;
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const mark_js_1 = require("../mark.js");
|
|
7
|
+
const cn_js_1 = require("../../cn.js");
|
|
7
8
|
/**
|
|
8
9
|
* The shared platform lockup: an application's own glyph on the CosmoOps badge,
|
|
9
10
|
* `cosmo·ops` beside it, and the application name underneath.
|
|
@@ -22,7 +23,7 @@ function ConnectLogo({ name, icon, variant, collapsed = false, className }) {
|
|
|
22
23
|
const resolved = variant ?? (collapsed ? "mark" : "full");
|
|
23
24
|
const badge = ((0, jsx_runtime_1.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 ? (0, jsx_runtime_1.jsx)("span", { className: "inline-flex items-center justify-center [&>*]:size-[1.25em]", children: icon }) : null, !icon && (0, jsx_runtime_1.jsx)(mark_js_1.ConnectMark, { className: "absolute inset-0 size-full" })] }));
|
|
24
25
|
if (resolved === "mark") {
|
|
25
|
-
return ((0, jsx_runtime_1.jsx)("span", { className:
|
|
26
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: (0, cn_js_1.cn)("inline-flex text-sm leading-none", className), role: "img", "aria-label": name, children: badge }));
|
|
26
27
|
}
|
|
27
|
-
return ((0, jsx_runtime_1.jsxs)("span", { className:
|
|
28
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: (0, cn_js_1.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, (0, jsx_runtime_1.jsxs)("span", { className: "flex min-w-0 flex-col leading-none", children: [(0, jsx_runtime_1.jsxs)("span", { className: "inline-flex items-center font-extrabold tracking-tight text-foreground", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("span", { children: "cosmo" }), (0, jsx_runtime_1.jsx)("span", { className: "mx-[0.12em] inline-block size-[0.19em] shrink-0 rounded-[0.04em] bg-accent" }), (0, jsx_runtime_1.jsx)("span", { children: "ops" })] }), (0, jsx_runtime_1.jsx)("span", { className: "mt-[0.42em] truncate text-[0.6em] font-semibold uppercase tracking-[0.26em] text-accent", "aria-hidden": "true", children: name })] })] }));
|
|
28
29
|
}
|
|
@@ -11,6 +11,7 @@ const mark_js_1 = require("../mark.js");
|
|
|
11
11
|
const identity_js_1 = require("./identity.js");
|
|
12
12
|
const motion_js_1 = require("./motion.js");
|
|
13
13
|
const one_tap_state_js_1 = require("./one-tap-state.js");
|
|
14
|
+
const cn_js_1 = require("../../cn.js");
|
|
14
15
|
function ConnectOneTap({ onInteractiveSignIn, className = "" }) {
|
|
15
16
|
const { session, isLoading, signIn, readOneTapHint, oneTapSignIn } = (0, provider_js_1.useConnect)();
|
|
16
17
|
const [hint, setHint] = (0, react_1.useState)(null);
|
|
@@ -95,7 +96,7 @@ function ConnectOneTap({ onInteractiveSignIn, className = "" }) {
|
|
|
95
96
|
// Fixed, above ordinary content but below a modal: this is an offer,
|
|
96
97
|
// and it must never sit on top of something the visitor opened
|
|
97
98
|
// themselves.
|
|
98
|
-
className:
|
|
99
|
+
className: (0, cn_js_1.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) => {
|
|
99
100
|
if (event.key === "Escape")
|
|
100
101
|
dismiss();
|
|
101
102
|
}, children: (0, jsx_runtime_1.jsxs)(react_2.Card, { className: "p-4 shadow-lg", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start gap-3", children: [hint.consented ? ((0, jsx_runtime_1.jsxs)(react_2.Avatar, { size: "md", color: "accent", variant: "soft", className: "shrink-0 rounded-full", children: [hint.user?.photoUrl && ((0, jsx_runtime_1.jsx)(react_2.Avatar.Image, { alt: displayName ?? "Your account", src: hint.user.photoUrl })), (0, jsx_runtime_1.jsx)(react_2.Avatar.Fallback, { children: (0, identity_js_1.initials)(hint.user?.displayName ?? null, hint.user?.email ?? null) })] })) : ((0, jsx_runtime_1.jsx)(mark_js_1.ConnectMark, { className: "mt-0.5 size-8 shrink-0" })), (0, jsx_runtime_1.jsxs)("div", { className: "min-w-0 flex-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "truncate text-sm font-medium text-foreground", children: hint.consented ? `Continue as ${displayName ?? "your account"}` : "You're signed in to CosmoOps" }), (0, jsx_runtime_1.jsx)("p", { className: "truncate text-xs text-muted", children: hint.consented
|
|
@@ -11,6 +11,7 @@ const use_checkout_js_1 = require("../use-checkout.js");
|
|
|
11
11
|
const motion_js_1 = require("./motion.js");
|
|
12
12
|
const subscribe_dialog_js_1 = require("./subscribe-dialog.js");
|
|
13
13
|
const plan_pricing_js_1 = require("./plan-pricing.js");
|
|
14
|
+
const cn_js_1 = require("../../cn.js");
|
|
14
15
|
/**
|
|
15
16
|
* The plan table: monthly/annual toggle, features, add-ons, the discount
|
|
16
17
|
* actually in effect, and the subscribe / upgrade / downgrade flows.
|
|
@@ -105,7 +106,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
105
106
|
setPending(null);
|
|
106
107
|
}
|
|
107
108
|
if (plansLoading || entitlementsLoading) {
|
|
108
|
-
return ((0, jsx_runtime_1.jsx)("div", { className:
|
|
109
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, cn_js_1.cn)("grid gap-4 sm:grid-cols-2 lg:grid-cols-3", className), children: Array.from({ length: 3 }, (_, index) => ((0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-80 rounded-xl" }, index))) }));
|
|
109
110
|
}
|
|
110
111
|
if (!session?.user) {
|
|
111
112
|
return (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-muted", children: "Sign in to see your plan options." });
|
|
@@ -113,7 +114,7 @@ function ConnectPlans({ planIds, hideCoupon = false, className }) {
|
|
|
113
114
|
if (error) {
|
|
114
115
|
return (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-danger", children: "Could not load plans." });
|
|
115
116
|
}
|
|
116
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className:
|
|
117
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex flex-col gap-6", className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [hasAnnual && hasMonthly && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm text-muted" : "text-sm font-medium text-foreground", children: "Monthly" }), (0, jsx_runtime_1.jsx)(react_1.Switch, { "aria-label": "Bill annually", isSelected: showAnnual, onChange: setAnnual, children: (0, jsx_runtime_1.jsx)(react_1.Switch.Content, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Control, { children: (0, jsx_runtime_1.jsx)(react_1.Switch.Thumb, {}) }) }) }), (0, jsx_runtime_1.jsx)("span", { className: showAnnual ? "text-sm font-medium text-foreground" : "text-sm text-muted", children: "Annual" }), bestSaving !== null && ((0, jsx_runtime_1.jsxs)(react_1.Chip, { className: "bg-accent-soft text-accent", children: ["Save up to ", bestSaving, "%"] }))] })), !hideCoupon && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Tag, { className: "size-4 text-muted", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)(react_1.Input, { "aria-label": "Coupon code", placeholder: "Coupon code", value: coupon, onChange: (event) => setCoupon(event.target.value.toUpperCase()), className: "w-40" })] }))] }), subscription?.subscription?.appliedDiscount && ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { className: "size-4 shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { children: [(0, plan_pricing_js_1.discountLabel)(subscription.subscription.appliedDiscount), subscription.subscription.appliedDiscount.code
|
|
117
118
|
? ` - code ${subscription.subscription.appliedDiscount.code}`
|
|
118
119
|
: ""] })] })), (0, jsx_runtime_1.jsx)(ConnectCheckoutStatusRow, { checkout: checkout }), (0, jsx_runtime_1.jsx)(motion_js_1.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) => {
|
|
119
120
|
// The price point this card is showing. A tier missing the toggled
|
|
@@ -9,6 +9,7 @@ const lucide_react_1 = require("lucide-react");
|
|
|
9
9
|
const helpers_js_1 = require("../../helpers.js");
|
|
10
10
|
const provider_js_1 = require("../provider.js");
|
|
11
11
|
const motion_js_1 = require("./motion.js");
|
|
12
|
+
const cn_js_1 = require("../../cn.js");
|
|
12
13
|
/**
|
|
13
14
|
* Codes that mean "somebody at Connect is looking at it" - there is no action
|
|
14
15
|
* for the customer to take, so these get an informational treatment and no
|
|
@@ -61,14 +62,14 @@ function ConnectReadinessBanner({ includeAdvisory = false, hideWhenReady = true,
|
|
|
61
62
|
if (!session?.user)
|
|
62
63
|
return null;
|
|
63
64
|
if (isLoading) {
|
|
64
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className:
|
|
65
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex items-center gap-2 text-sm text-muted", className), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), "Checking your workspace\u2026"] }));
|
|
65
66
|
}
|
|
66
67
|
if (!data)
|
|
67
68
|
return null;
|
|
68
69
|
const blockers = includeAdvisory ? data.readiness.readiness.blockers : (0, helpers_js_1.blockingBlockers)(data.readiness);
|
|
69
70
|
if (blockers.length === 0)
|
|
70
71
|
return hideWhenReady ? null : (0, jsx_runtime_1.jsx)(ReadyRow, { className: className });
|
|
71
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className:
|
|
72
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { className: (0, cn_js_1.cn)("flex flex-col gap-2", className), role: "status", initial: { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, transition: motion_js_1.ENTER, children: blockers.map((blocker) => ((0, jsx_runtime_1.jsx)(BlockerRow, { blocker: blocker }, `${blocker.code}-${blocker.message}`))) }) }));
|
|
72
73
|
}
|
|
73
74
|
function BlockerRow({ blocker }) {
|
|
74
75
|
const inProgress = IN_PROGRESS_CODES.has(blocker.code);
|
|
@@ -76,7 +77,7 @@ function BlockerRow({ blocker }) {
|
|
|
76
77
|
return ((0, jsx_runtime_1.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: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [inProgress || !isBlocking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Info, { className: "size-4 shrink-0", "aria-hidden": "true" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.AlertTriangle, { className: "size-4 shrink-0", "aria-hidden": "true" })), blocker.message] }), blocker.resolveUrl && !inProgress && ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(blocker.resolveUrl, "_blank", "noopener,noreferrer"), children: [actionLabel(blocker), (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }))] }));
|
|
77
78
|
}
|
|
78
79
|
function ReadyRow({ className }) {
|
|
79
|
-
return ((0, jsx_runtime_1.jsx)("p", { className:
|
|
80
|
+
return ((0, jsx_runtime_1.jsx)("p", { className: (0, cn_js_1.cn)("text-sm text-muted", className), children: "This workspace is cleared to use every part of the application." }));
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Renders `children` only once the workspace clears every blocking gate.
|
|
@@ -9,6 +9,7 @@ exports.SidebarLabel = SidebarLabel;
|
|
|
9
9
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
10
10
|
const react_1 = require("react");
|
|
11
11
|
const motion_js_1 = require("./motion.js");
|
|
12
|
+
const cn_js_1 = require("../../cn.js");
|
|
12
13
|
/** A plain anchor - correct anywhere, and a full page load. See `ConnectLinkRenderer`. */
|
|
13
14
|
const defaultRenderLink = ({ children, ...props }) => (0, jsx_runtime_1.jsx)("a", { ...props, children: children });
|
|
14
15
|
exports.defaultRenderLink = defaultRenderLink;
|
|
@@ -145,5 +146,5 @@ function useConnectShell() {
|
|
|
145
146
|
*/
|
|
146
147
|
function SidebarLabel({ children, className, }) {
|
|
147
148
|
const { collapsed } = useConnectShell();
|
|
148
|
-
return ((0, jsx_runtime_1.jsx)(motion_js_1.m.span, { className:
|
|
149
|
+
return ((0, jsx_runtime_1.jsx)(motion_js_1.m.span, { className: (0, cn_js_1.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 }));
|
|
149
150
|
}
|
|
@@ -10,6 +10,7 @@ const motion_js_1 = require("./motion.js");
|
|
|
10
10
|
const shell_context_js_1 = require("./shell-context.js");
|
|
11
11
|
Object.defineProperty(exports, "defaultRenderLink", { enumerable: true, get: function () { return shell_context_js_1.defaultRenderLink; } });
|
|
12
12
|
const use_active_path_js_1 = require("../use-active-path.js");
|
|
13
|
+
const cn_js_1 = require("../../cn.js");
|
|
13
14
|
const nav_js_1 = require("./nav.js");
|
|
14
15
|
function rowClass(isActive, depth) {
|
|
15
16
|
// The indent is a *margin*, not padding, so a child row steps in while the
|
|
@@ -68,7 +69,7 @@ function ConnectSidebarNav({ nav, activePath, renderLink, collapsed, onNavigate,
|
|
|
68
69
|
const pathname = (0, use_active_path_js_1.useActivePath)(activePath ?? shell.activePath);
|
|
69
70
|
const active = (0, nav_js_1.resolveActivePath)(pathname, nav);
|
|
70
71
|
const sections = (0, nav_js_1.normalizeNav)(nav);
|
|
71
|
-
return ((0, jsx_runtime_1.jsx)("div", { className:
|
|
72
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, cn_js_1.cn)("flex flex-col gap-2", className), children: sections.map((section, index) => ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": section.title ?? "Main", className: "flex flex-col gap-1", children: [section.title && (0, jsx_runtime_1.jsx)(SectionHeading, { title: section.title, collapsed: isCollapsed }), section.items.map((item) => {
|
|
72
73
|
const isActive = (0, nav_js_1.isNavItemActive)(item, active);
|
|
73
74
|
const activeChild = (item.items ?? []).some((child) => (0, nav_js_1.isNavItemActive)(child, active));
|
|
74
75
|
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex w-full flex-col gap-1", children: [(0, jsx_runtime_1.jsx)(NavRow, { item: item, isActive: isActive, hasActiveChild: activeChild, collapsed: isCollapsed, depth: 0, renderLink: link, onNavigate: navigate }), !isCollapsed &&
|
|
@@ -12,6 +12,7 @@ const lucide_react_1 = require("lucide-react");
|
|
|
12
12
|
const logo_js_1 = require("./logo.js");
|
|
13
13
|
const motion_js_1 = require("./motion.js");
|
|
14
14
|
const shell_context_js_1 = require("./shell-context.js");
|
|
15
|
+
const cn_js_1 = require("../../cn.js");
|
|
15
16
|
/**
|
|
16
17
|
* The rail's own transition.
|
|
17
18
|
*
|
|
@@ -50,7 +51,7 @@ function ConnectSidebar({ children, label = "Sidebar", className }) {
|
|
|
50
51
|
// from these parts by hand gets a sidebar that jumps between widths
|
|
51
52
|
// instead of animating - a failure that looks like a styling bug and is
|
|
52
53
|
// really a missing provider.
|
|
53
|
-
(0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.aside, { "aria-label": label, className:
|
|
54
|
+
(0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.aside, { "aria-label": label, className: (0, cn_js_1.cn)("hidden shrink-0 flex-col overflow-hidden border-e border-border bg-surface md:flex", className),
|
|
54
55
|
// `initial={false}` is what stops the rail sweeping open on first paint
|
|
55
56
|
// for a returning user whose stored preference is "collapsed".
|
|
56
57
|
initial: false, animate: { width: collapsed ? shell_context_js_1.RAIL_COLLAPSED : shell_context_js_1.RAIL_EXPANDED }, transition: RAIL_TRANSITION, children: children }) }));
|
|
@@ -14,6 +14,7 @@ const provider_js_1 = require("../provider.js");
|
|
|
14
14
|
const motion_js_1 = require("./motion.js");
|
|
15
15
|
const plan_pricing_js_1 = require("./plan-pricing.js");
|
|
16
16
|
const link_button_js_1 = require("./link-button.js");
|
|
17
|
+
const cn_js_1 = require("../../cn.js");
|
|
17
18
|
/** Where the plan catalogue lives in the host app. Overridable, because not every app mounts it at `/plans`. */
|
|
18
19
|
const DEFAULT_PLANS_HREF = "/plans";
|
|
19
20
|
function formatDate(iso) {
|
|
@@ -82,7 +83,7 @@ function ConnectSubscriptionCard({ plansHref, className }) {
|
|
|
82
83
|
const { appUrl } = (0, provider_js_1.useConnect)();
|
|
83
84
|
const { isLoading, detail, plan, state } = useSubscriptionState();
|
|
84
85
|
if (isLoading)
|
|
85
|
-
return (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className:
|
|
86
|
+
return (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: (0, cn_js_1.cn)("h-48 rounded-xl", className) });
|
|
86
87
|
if (!detail || !plan)
|
|
87
88
|
return (0, jsx_runtime_1.jsx)(ConnectNoSubscriptionCard, { plansHref: plansHref, className: className });
|
|
88
89
|
return ((0, jsx_runtime_1.jsx)(motion_js_1.MotionProvider, { children: (0, jsx_runtime_1.jsx)(motion_js_1.m.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.2 }, children: (0, jsx_runtime_1.jsxs)(react_1.Card, { className: className, children: [(0, jsx_runtime_1.jsxs)(react_1.Card.Header, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.Card.Title, { children: plan.name }), (0, jsx_runtime_1.jsx)(SubscriptionChip, { state: state })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Description, { children: [(0, plan_pricing_js_1.formatPrice)(plan.price), " + ", plan.gstRate, "% GST per", " ", (0, plan_pricing_js_1.intervalLabel)(plan.billingInterval).per] })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Content, { className: "flex flex-col gap-2 text-sm", children: [(0, jsx_runtime_1.jsx)(NextEvent, { detail: detail, state: state }), (0, jsx_runtime_1.jsx)(ConnectSeatUsage, { plan: plan, detail: detail })] }), (0, jsx_runtime_1.jsxs)(react_1.Card.Footer, { className: "flex flex-wrap gap-2", children: [(0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(`${appUrl}/billing/subscriptions/${detail.id}`, "_blank", "noopener,noreferrer"), children: ["Manage on Connect", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] }), (0, jsx_runtime_1.jsx)(link_button_js_1.ConnectLinkButton, { href: plansHref ?? DEFAULT_PLANS_HREF, variant: "tertiary", children: "Change plan" })] })] }) }) }));
|
|
@@ -132,7 +133,7 @@ function ConnectSeatUsage({ plan, detail, className, }) {
|
|
|
132
133
|
return null;
|
|
133
134
|
const included = plan.seats.included ?? 0;
|
|
134
135
|
const over = detail.seatCount - included;
|
|
135
|
-
return ((0, jsx_runtime_1.jsxs)("span", { className:
|
|
136
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: (0, cn_js_1.cn)("flex items-center gap-2 text-muted", className), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Users, { className: "size-4 shrink-0", "aria-hidden": "true" }), detail.seatCount, " of ", included, " seat", included === 1 ? "" : "s", " used", over > 0 &&
|
|
136
137
|
(plan.seats.model === "per-seat"
|
|
137
138
|
? ` - ${over} extra at ${(0, plan_pricing_js_1.formatPrice)(plan.seats.pricePerExtraSeat ?? 0)} each`
|
|
138
139
|
: ` - ${over} over the plan's limit`)] }));
|
|
@@ -154,7 +155,7 @@ function ConnectTrialBanner({ plansHref, className }) {
|
|
|
154
155
|
const { detail, state } = useSubscriptionState();
|
|
155
156
|
if (state !== "trialing" || !detail)
|
|
156
157
|
return null;
|
|
157
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className:
|
|
158
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex flex-wrap items-center justify-between gap-3 rounded-lg bg-accent-soft px-3 py-2 text-sm text-accent", className), children: [(0, jsx_runtime_1.jsxs)("span", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Clock, { className: "size-4 shrink-0", "aria-hidden": "true" }), "You're on a free trial", detail.nextBillingDate ? `. Your first charge is on ${formatDate(detail.nextBillingDate)}.` : "."] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(link_button_js_1.ConnectLinkButton, { href: plansHref ?? DEFAULT_PLANS_HREF, variant: "tertiary", children: "Compare plans" }), (0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "secondary", onPress: () => window.open(`${appUrl}/billing/subscriptions/${detail.id}`, "_blank", "noopener,noreferrer"), children: ["Manage", (0, jsx_runtime_1.jsx)(lucide_react_1.ExternalLink, { className: "size-3.5" })] })] })] }));
|
|
158
159
|
}
|
|
159
160
|
/**
|
|
160
161
|
* Renders `children` for a subscribed workspace, and the "pick a plan" card for
|
|
@@ -174,7 +175,7 @@ function ConnectSubscriptionGate({ children, plansHref, fallback, loading, allow
|
|
|
174
175
|
const { session, isLoading: sessionLoading } = (0, provider_js_1.useConnect)();
|
|
175
176
|
const { isLoading, state } = useSubscriptionState();
|
|
176
177
|
if (sessionLoading || (session?.user && isLoading)) {
|
|
177
|
-
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading ?? (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className:
|
|
178
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: loading ?? (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: (0, cn_js_1.cn)("h-48 rounded-xl", className) }) });
|
|
178
179
|
}
|
|
179
180
|
if (!session?.user)
|
|
180
181
|
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback ?? null });
|
|
@@ -8,6 +8,7 @@ const lucide_react_1 = require("lucide-react");
|
|
|
8
8
|
const provider_js_1 = require("../provider.js");
|
|
9
9
|
const motion_js_1 = require("./motion.js");
|
|
10
10
|
const plan_pricing_js_1 = require("./plan-pricing.js");
|
|
11
|
+
const cn_js_1 = require("../../cn.js");
|
|
11
12
|
function formatUnits(value, unit) {
|
|
12
13
|
return `${value.toLocaleString("en-IN")} ${unit}`;
|
|
13
14
|
}
|
|
@@ -35,7 +36,7 @@ function ConnectUsageMeter({ addonIds, hideWhenEmpty = true, title = "Usage this
|
|
|
35
36
|
if (!session?.user)
|
|
36
37
|
return null;
|
|
37
38
|
if (isLoading)
|
|
38
|
-
return (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className:
|
|
39
|
+
return (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: (0, cn_js_1.cn)("h-40 rounded-xl", className) });
|
|
39
40
|
if (error || !data)
|
|
40
41
|
return null;
|
|
41
42
|
// Defended here as well as in `billing.getAddonUsage`, and deliberately so:
|
|
@@ -19,6 +19,7 @@ const account_sessions_js_1 = require("./account-sessions.js");
|
|
|
19
19
|
const motion_js_1 = require("./motion.js");
|
|
20
20
|
const shell_context_js_1 = require("./shell-context.js");
|
|
21
21
|
const surfaces_js_1 = require("./surfaces.js");
|
|
22
|
+
const cn_js_1 = require("../../cn.js");
|
|
22
23
|
const THEME_OPTIONS = [
|
|
23
24
|
{ value: "light", label: "Light", icon: lucide_react_1.Sun },
|
|
24
25
|
{ value: "dark", label: "Dark", icon: lucide_react_1.Moon },
|
|
@@ -199,7 +200,7 @@ function ConnectUserButton({ variant = "row", onSignOut, className, theme, onThe
|
|
|
199
200
|
const { session, isLoading } = (0, provider_js_1.useConnect)();
|
|
200
201
|
const { collapsed } = (0, shell_context_js_1.useConnectShell)();
|
|
201
202
|
if (isLoading) {
|
|
202
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className:
|
|
203
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_js_1.cn)("flex h-10 items-center gap-3", variant === "row" ? "ps-[4px]" : "", className), children: [(0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "size-8 shrink-0 rounded-full" }), variant !== "compact" && !collapsed && (0, jsx_runtime_1.jsx)(react_1.Skeleton, { className: "h-4 w-24" })] }));
|
|
203
204
|
}
|
|
204
205
|
const user = session?.user;
|
|
205
206
|
if (!user)
|
|
@@ -209,7 +210,7 @@ function ConnectUserButton({ variant = "row", onSignOut, className, theme, onThe
|
|
|
209
210
|
// `ps-1` against the default trailing padding is optical, not
|
|
210
211
|
// sloppy: a circular avatar needs less inset than a text label to
|
|
211
212
|
// look evenly seated in the button.
|
|
212
|
-
className:
|
|
213
|
+
className: (0, cn_js_1.cn)("gap-2 ps-1", className), children: [(0, jsx_runtime_1.jsx)(UserAvatar, { user: user, size: "sm" }), variant === "inline" && (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-medium text-foreground", children: label })] })), (0, jsx_runtime_1.jsx)(ProfileSurface, { onSignOut: onSignOut, theme: theme, onThemeChange: onThemeChange })] }));
|
|
213
214
|
}
|
|
214
215
|
/**
|
|
215
216
|
* @deprecated Renamed to `ConnectUserButton` in 0.12 - it is one button that
|