@augmenting-integrations/ui 4.0.2 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ "use client";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var AppRegistryForm_exports = {};
31
+ __export(AppRegistryForm_exports, {
32
+ AppRegistryForm: () => AppRegistryForm
33
+ });
34
+ module.exports = __toCommonJS(AppRegistryForm_exports);
35
+ var import_jsx_runtime = require("react/jsx-runtime");
36
+ var React = __toESM(require("react"));
37
+ var import_button = require("../ui/button.js");
38
+ var import_input = require("../ui/input.js");
39
+ var import_label = require("../ui/label.js");
40
+ var import_textarea = require("../ui/textarea.js");
41
+ function kebab(s) {
42
+ return s.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
43
+ }
44
+ function AppRegistryForm({
45
+ availableGroups,
46
+ initial,
47
+ defaultNavOrder = 10,
48
+ onSubmit,
49
+ busy = false,
50
+ submitLabel = "Save"
51
+ }) {
52
+ const [displayName, setDisplayName] = React.useState(initial?.displayName ?? "");
53
+ const [slug, setSlug] = React.useState(initial?.slug ?? "");
54
+ const [slugTouched, setSlugTouched] = React.useState(!!initial?.slug);
55
+ const [subdomain, setSubdomain] = React.useState(initial?.subdomain ?? "");
56
+ const [subdomainTouched, setSubdomainTouched] = React.useState(
57
+ initial?.subdomain !== void 0 && initial?.subdomain !== ""
58
+ );
59
+ const [navOrder, setNavOrder] = React.useState(
60
+ initial?.navOrder ?? defaultNavOrder
61
+ );
62
+ const [requiredGroups, setRequiredGroups] = React.useState(
63
+ initial?.requiredGroups ?? []
64
+ );
65
+ const [description, setDescription] = React.useState(initial?.description ?? "");
66
+ React.useEffect(() => {
67
+ if (!slugTouched) setSlug(kebab(displayName));
68
+ }, [displayName, slugTouched]);
69
+ React.useEffect(() => {
70
+ if (!subdomainTouched) setSubdomain(kebab(slug));
71
+ }, [slug, subdomainTouched]);
72
+ const toggleGroup = (g) => {
73
+ setRequiredGroups(
74
+ (prev) => prev.includes(g) ? prev.filter((x) => x !== g) : [...prev, g]
75
+ );
76
+ };
77
+ const handleSubmit = async (e) => {
78
+ e.preventDefault();
79
+ await onSubmit({
80
+ displayName: displayName.trim(),
81
+ slug: slug.trim(),
82
+ subdomain: subdomain.trim(),
83
+ navOrder,
84
+ requiredGroups,
85
+ description: description.trim()
86
+ });
87
+ };
88
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-5", children: [
89
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
90
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { htmlFor: "display-name", children: "Display name" }),
91
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
92
+ import_input.Input,
93
+ {
94
+ id: "display-name",
95
+ value: displayName,
96
+ onChange: (e) => setDisplayName(e.target.value),
97
+ placeholder: "Claims Processing",
98
+ required: true
99
+ }
100
+ ),
101
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-muted-foreground", children: "Shown in the nav and admin list. Slug and subdomain are auto-derived." })
102
+ ] }),
103
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { htmlFor: "slug", children: "Slug" }),
105
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
106
+ import_input.Input,
107
+ {
108
+ id: "slug",
109
+ value: slug,
110
+ onChange: (e) => {
111
+ setSlugTouched(true);
112
+ setSlug(e.target.value);
113
+ },
114
+ placeholder: "claims-processing",
115
+ required: true,
116
+ pattern: "^[a-z0-9][a-z0-9-]*$"
117
+ }
118
+ ),
119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
120
+ "Stable identifier (kebab-case). Must match",
121
+ " ",
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "font-mono", children: "APP_SLUG" }),
123
+ " in the app's .env."
124
+ ] })
125
+ ] }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { htmlFor: "subdomain", children: "Subdomain" }),
128
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
129
+ import_input.Input,
130
+ {
131
+ id: "subdomain",
132
+ value: subdomain,
133
+ onChange: (e) => {
134
+ setSubdomainTouched(true);
135
+ setSubdomain(e.target.value);
136
+ },
137
+ placeholder: "claims-processing",
138
+ pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
139
+ }
140
+ ),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
142
+ "Leave empty for the apex app. Otherwise this becomes",
143
+ " ",
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { className: "font-mono", children: "<subdomain>.<apex>" }),
145
+ "."
146
+ ] })
147
+ ] }),
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { htmlFor: "nav-order", children: "Nav order" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
151
+ import_input.Input,
152
+ {
153
+ id: "nav-order",
154
+ type: "number",
155
+ value: navOrder,
156
+ onChange: (e) => setNavOrder(Number(e.target.value))
157
+ }
158
+ ),
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-muted-foreground", children: "Lower numbers come first." })
160
+ ] }),
161
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { children: "Required groups" }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex flex-wrap gap-2", children: availableGroups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-muted-foreground", children: "No groups available." }) : availableGroups.map((g) => {
164
+ const checked = requiredGroups.includes(g);
165
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
166
+ "button",
167
+ {
168
+ type: "button",
169
+ onClick: () => toggleGroup(g),
170
+ className: checked ? "rounded-full border border-primary bg-primary px-3 py-1 text-xs text-primary-foreground" : "rounded-full border border-border px-3 py-1 text-xs",
171
+ children: g
172
+ },
173
+ g
174
+ );
175
+ }) }),
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-muted-foreground", children: "Empty = any authenticated user can see this app." })
177
+ ] }),
178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { htmlFor: "description", children: "Description" }),
180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
181
+ import_textarea.Textarea,
182
+ {
183
+ id: "description",
184
+ value: description,
185
+ onChange: (e) => setDescription(e.target.value),
186
+ rows: 3,
187
+ placeholder: "Optional notes shown in the admin UI."
188
+ }
189
+ )
190
+ ] }),
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { type: "submit", disabled: busy, children: busy ? "Saving..." : submitLabel }) })
192
+ ] });
193
+ }
194
+ // Annotate the CommonJS export names for ESM import in node:
195
+ 0 && (module.exports = {
196
+ AppRegistryForm
197
+ });
198
+ //# sourceMappingURL=AppRegistryForm.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/admin/AppRegistryForm.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Button } from \"../ui/button.js\";\nimport { Input } from \"../ui/input.js\";\nimport { Label } from \"../ui/label.js\";\nimport { Textarea } from \"../ui/textarea.js\";\n\n// Auto-derivation: displayName -> slug -> subdomain.\n// \"Claims Processing\" -> \"claims-processing\" -> \"claims-processing\"\n// User can override slug and subdomain independently.\nfunction kebab(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 40);\n}\n\nexport interface AppRegistryFormValue {\n displayName: string;\n slug: string;\n subdomain: string;\n navOrder: number;\n requiredGroups: string[];\n description: string;\n}\n\nexport interface AppRegistryFormProps {\n /** Available Cognito groups to choose from for `requiredGroups`. */\n availableGroups: string[];\n /** Initial values (for edit forms). Empty defaults make this an add form. */\n initial?: Partial<AppRegistryFormValue>;\n /** Default navOrder for new rows (e.g. max+10 derived in the page). */\n defaultNavOrder?: number;\n /** Submit handler; the page wires this to its admin API. */\n onSubmit: (value: AppRegistryFormValue) => Promise<void> | void;\n /** Disable while submitting. */\n busy?: boolean;\n submitLabel?: string;\n}\n\nexport function AppRegistryForm({\n availableGroups,\n initial,\n defaultNavOrder = 10,\n onSubmit,\n busy = false,\n submitLabel = \"Save\",\n}: AppRegistryFormProps) {\n const [displayName, setDisplayName] = React.useState(initial?.displayName ?? \"\");\n const [slug, setSlug] = React.useState(initial?.slug ?? \"\");\n const [slugTouched, setSlugTouched] = React.useState(!!initial?.slug);\n const [subdomain, setSubdomain] = React.useState(initial?.subdomain ?? \"\");\n const [subdomainTouched, setSubdomainTouched] = React.useState(\n initial?.subdomain !== undefined && initial?.subdomain !== \"\",\n );\n const [navOrder, setNavOrder] = React.useState<number>(\n initial?.navOrder ?? defaultNavOrder,\n );\n const [requiredGroups, setRequiredGroups] = React.useState<string[]>(\n initial?.requiredGroups ?? [],\n );\n const [description, setDescription] = React.useState(initial?.description ?? \"\");\n\n // Auto-derive slug and subdomain from displayName until the user edits them.\n React.useEffect(() => {\n if (!slugTouched) setSlug(kebab(displayName));\n }, [displayName, slugTouched]);\n React.useEffect(() => {\n if (!subdomainTouched) setSubdomain(kebab(slug));\n }, [slug, subdomainTouched]);\n\n const toggleGroup = (g: string) => {\n setRequiredGroups((prev) =>\n prev.includes(g) ? prev.filter((x) => x !== g) : [...prev, g],\n );\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n await onSubmit({\n displayName: displayName.trim(),\n slug: slug.trim(),\n subdomain: subdomain.trim(),\n navOrder,\n requiredGroups,\n description: description.trim(),\n });\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"display-name\">Display name</Label>\n <Input\n id=\"display-name\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n placeholder=\"Claims Processing\"\n required\n />\n <p className=\"text-xs text-muted-foreground\">\n Shown in the nav and admin list. Slug and subdomain are auto-derived.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"slug\">Slug</Label>\n <Input\n id=\"slug\"\n value={slug}\n onChange={(e) => {\n setSlugTouched(true);\n setSlug(e.target.value);\n }}\n placeholder=\"claims-processing\"\n required\n pattern=\"^[a-z0-9][a-z0-9-]*$\"\n />\n <p className=\"text-xs text-muted-foreground\">\n Stable identifier (kebab-case). Must match{\" \"}\n <code className=\"font-mono\">APP_SLUG</code> in the app's .env.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"subdomain\">Subdomain</Label>\n <Input\n id=\"subdomain\"\n value={subdomain}\n onChange={(e) => {\n setSubdomainTouched(true);\n setSubdomain(e.target.value);\n }}\n placeholder=\"claims-processing\"\n pattern=\"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$\"\n />\n <p className=\"text-xs text-muted-foreground\">\n Leave empty for the apex app. Otherwise this becomes{\" \"}\n <code className=\"font-mono\">&lt;subdomain&gt;.&lt;apex&gt;</code>.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"nav-order\">Nav order</Label>\n <Input\n id=\"nav-order\"\n type=\"number\"\n value={navOrder}\n onChange={(e) => setNavOrder(Number(e.target.value))}\n />\n <p className=\"text-xs text-muted-foreground\">Lower numbers come first.</p>\n </div>\n\n <div className=\"space-y-2\">\n <Label>Required groups</Label>\n <div className=\"flex flex-wrap gap-2\">\n {availableGroups.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">No groups available.</p>\n ) : (\n availableGroups.map((g) => {\n const checked = requiredGroups.includes(g);\n return (\n <button\n type=\"button\"\n key={g}\n onClick={() => toggleGroup(g)}\n className={\n checked\n ? \"rounded-full border border-primary bg-primary px-3 py-1 text-xs text-primary-foreground\"\n : \"rounded-full border border-border px-3 py-1 text-xs\"\n }\n >\n {g}\n </button>\n );\n })\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n Empty = any authenticated user can see this app.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"description\">Description</Label>\n <Textarea\n id=\"description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n placeholder=\"Optional notes shown in the admin UI.\"\n />\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={busy}>\n {busy ? \"Saving...\" : submitLabel}\n </Button>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ export interface AppRegistryFormValue {
3
+ displayName: string;
4
+ slug: string;
5
+ subdomain: string;
6
+ navOrder: number;
7
+ requiredGroups: string[];
8
+ description: string;
9
+ }
10
+ export interface AppRegistryFormProps {
11
+ /** Available Cognito groups to choose from for `requiredGroups`. */
12
+ availableGroups: string[];
13
+ /** Initial values (for edit forms). Empty defaults make this an add form. */
14
+ initial?: Partial<AppRegistryFormValue>;
15
+ /** Default navOrder for new rows (e.g. max+10 derived in the page). */
16
+ defaultNavOrder?: number;
17
+ /** Submit handler; the page wires this to its admin API. */
18
+ onSubmit: (value: AppRegistryFormValue) => Promise<void> | void;
19
+ /** Disable while submitting. */
20
+ busy?: boolean;
21
+ submitLabel?: string;
22
+ }
23
+ export declare function AppRegistryForm({ availableGroups, initial, defaultNavOrder, onSubmit, busy, submitLabel, }: AppRegistryFormProps): React.JSX.Element;
24
+ //# sourceMappingURL=AppRegistryForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppRegistryForm.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AppRegistryForm.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAkB/B,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACxC,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAChE,gCAAgC;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,EAC9B,eAAe,EACf,OAAO,EACP,eAAoB,EACpB,QAAQ,EACR,IAAY,EACZ,WAAoB,GACrB,EAAE,oBAAoB,qBA0JtB"}
@@ -0,0 +1,164 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Button } from "../ui/button.js";
5
+ import { Input } from "../ui/input.js";
6
+ import { Label } from "../ui/label.js";
7
+ import { Textarea } from "../ui/textarea.js";
8
+ function kebab(s) {
9
+ return s.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
10
+ }
11
+ function AppRegistryForm({
12
+ availableGroups,
13
+ initial,
14
+ defaultNavOrder = 10,
15
+ onSubmit,
16
+ busy = false,
17
+ submitLabel = "Save"
18
+ }) {
19
+ const [displayName, setDisplayName] = React.useState(initial?.displayName ?? "");
20
+ const [slug, setSlug] = React.useState(initial?.slug ?? "");
21
+ const [slugTouched, setSlugTouched] = React.useState(!!initial?.slug);
22
+ const [subdomain, setSubdomain] = React.useState(initial?.subdomain ?? "");
23
+ const [subdomainTouched, setSubdomainTouched] = React.useState(
24
+ initial?.subdomain !== void 0 && initial?.subdomain !== ""
25
+ );
26
+ const [navOrder, setNavOrder] = React.useState(
27
+ initial?.navOrder ?? defaultNavOrder
28
+ );
29
+ const [requiredGroups, setRequiredGroups] = React.useState(
30
+ initial?.requiredGroups ?? []
31
+ );
32
+ const [description, setDescription] = React.useState(initial?.description ?? "");
33
+ React.useEffect(() => {
34
+ if (!slugTouched) setSlug(kebab(displayName));
35
+ }, [displayName, slugTouched]);
36
+ React.useEffect(() => {
37
+ if (!subdomainTouched) setSubdomain(kebab(slug));
38
+ }, [slug, subdomainTouched]);
39
+ const toggleGroup = (g) => {
40
+ setRequiredGroups(
41
+ (prev) => prev.includes(g) ? prev.filter((x) => x !== g) : [...prev, g]
42
+ );
43
+ };
44
+ const handleSubmit = async (e) => {
45
+ e.preventDefault();
46
+ await onSubmit({
47
+ displayName: displayName.trim(),
48
+ slug: slug.trim(),
49
+ subdomain: subdomain.trim(),
50
+ navOrder,
51
+ requiredGroups,
52
+ description: description.trim()
53
+ });
54
+ };
55
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-5", children: [
56
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
57
+ /* @__PURE__ */ jsx(Label, { htmlFor: "display-name", children: "Display name" }),
58
+ /* @__PURE__ */ jsx(
59
+ Input,
60
+ {
61
+ id: "display-name",
62
+ value: displayName,
63
+ onChange: (e) => setDisplayName(e.target.value),
64
+ placeholder: "Claims Processing",
65
+ required: true
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Shown in the nav and admin list. Slug and subdomain are auto-derived." })
69
+ ] }),
70
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
71
+ /* @__PURE__ */ jsx(Label, { htmlFor: "slug", children: "Slug" }),
72
+ /* @__PURE__ */ jsx(
73
+ Input,
74
+ {
75
+ id: "slug",
76
+ value: slug,
77
+ onChange: (e) => {
78
+ setSlugTouched(true);
79
+ setSlug(e.target.value);
80
+ },
81
+ placeholder: "claims-processing",
82
+ required: true,
83
+ pattern: "^[a-z0-9][a-z0-9-]*$"
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
87
+ "Stable identifier (kebab-case). Must match",
88
+ " ",
89
+ /* @__PURE__ */ jsx("code", { className: "font-mono", children: "APP_SLUG" }),
90
+ " in the app's .env."
91
+ ] })
92
+ ] }),
93
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
94
+ /* @__PURE__ */ jsx(Label, { htmlFor: "subdomain", children: "Subdomain" }),
95
+ /* @__PURE__ */ jsx(
96
+ Input,
97
+ {
98
+ id: "subdomain",
99
+ value: subdomain,
100
+ onChange: (e) => {
101
+ setSubdomainTouched(true);
102
+ setSubdomain(e.target.value);
103
+ },
104
+ placeholder: "claims-processing",
105
+ pattern: "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
106
+ }
107
+ ),
108
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
109
+ "Leave empty for the apex app. Otherwise this becomes",
110
+ " ",
111
+ /* @__PURE__ */ jsx("code", { className: "font-mono", children: "<subdomain>.<apex>" }),
112
+ "."
113
+ ] })
114
+ ] }),
115
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
116
+ /* @__PURE__ */ jsx(Label, { htmlFor: "nav-order", children: "Nav order" }),
117
+ /* @__PURE__ */ jsx(
118
+ Input,
119
+ {
120
+ id: "nav-order",
121
+ type: "number",
122
+ value: navOrder,
123
+ onChange: (e) => setNavOrder(Number(e.target.value))
124
+ }
125
+ ),
126
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Lower numbers come first." })
127
+ ] }),
128
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
129
+ /* @__PURE__ */ jsx(Label, { children: "Required groups" }),
130
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: availableGroups.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No groups available." }) : availableGroups.map((g) => {
131
+ const checked = requiredGroups.includes(g);
132
+ return /* @__PURE__ */ jsx(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ onClick: () => toggleGroup(g),
137
+ className: checked ? "rounded-full border border-primary bg-primary px-3 py-1 text-xs text-primary-foreground" : "rounded-full border border-border px-3 py-1 text-xs",
138
+ children: g
139
+ },
140
+ g
141
+ );
142
+ }) }),
143
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Empty = any authenticated user can see this app." })
144
+ ] }),
145
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
146
+ /* @__PURE__ */ jsx(Label, { htmlFor: "description", children: "Description" }),
147
+ /* @__PURE__ */ jsx(
148
+ Textarea,
149
+ {
150
+ id: "description",
151
+ value: description,
152
+ onChange: (e) => setDescription(e.target.value),
153
+ rows: 3,
154
+ placeholder: "Optional notes shown in the admin UI."
155
+ }
156
+ )
157
+ ] }),
158
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Button, { type: "submit", disabled: busy, children: busy ? "Saving..." : submitLabel }) })
159
+ ] });
160
+ }
161
+ export {
162
+ AppRegistryForm
163
+ };
164
+ //# sourceMappingURL=AppRegistryForm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/admin/AppRegistryForm.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Button } from \"../ui/button.js\";\nimport { Input } from \"../ui/input.js\";\nimport { Label } from \"../ui/label.js\";\nimport { Textarea } from \"../ui/textarea.js\";\n\n// Auto-derivation: displayName -> slug -> subdomain.\n// \"Claims Processing\" -> \"claims-processing\" -> \"claims-processing\"\n// User can override slug and subdomain independently.\nfunction kebab(s: string): string {\n return s\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 40);\n}\n\nexport interface AppRegistryFormValue {\n displayName: string;\n slug: string;\n subdomain: string;\n navOrder: number;\n requiredGroups: string[];\n description: string;\n}\n\nexport interface AppRegistryFormProps {\n /** Available Cognito groups to choose from for `requiredGroups`. */\n availableGroups: string[];\n /** Initial values (for edit forms). Empty defaults make this an add form. */\n initial?: Partial<AppRegistryFormValue>;\n /** Default navOrder for new rows (e.g. max+10 derived in the page). */\n defaultNavOrder?: number;\n /** Submit handler; the page wires this to its admin API. */\n onSubmit: (value: AppRegistryFormValue) => Promise<void> | void;\n /** Disable while submitting. */\n busy?: boolean;\n submitLabel?: string;\n}\n\nexport function AppRegistryForm({\n availableGroups,\n initial,\n defaultNavOrder = 10,\n onSubmit,\n busy = false,\n submitLabel = \"Save\",\n}: AppRegistryFormProps) {\n const [displayName, setDisplayName] = React.useState(initial?.displayName ?? \"\");\n const [slug, setSlug] = React.useState(initial?.slug ?? \"\");\n const [slugTouched, setSlugTouched] = React.useState(!!initial?.slug);\n const [subdomain, setSubdomain] = React.useState(initial?.subdomain ?? \"\");\n const [subdomainTouched, setSubdomainTouched] = React.useState(\n initial?.subdomain !== undefined && initial?.subdomain !== \"\",\n );\n const [navOrder, setNavOrder] = React.useState<number>(\n initial?.navOrder ?? defaultNavOrder,\n );\n const [requiredGroups, setRequiredGroups] = React.useState<string[]>(\n initial?.requiredGroups ?? [],\n );\n const [description, setDescription] = React.useState(initial?.description ?? \"\");\n\n // Auto-derive slug and subdomain from displayName until the user edits them.\n React.useEffect(() => {\n if (!slugTouched) setSlug(kebab(displayName));\n }, [displayName, slugTouched]);\n React.useEffect(() => {\n if (!subdomainTouched) setSubdomain(kebab(slug));\n }, [slug, subdomainTouched]);\n\n const toggleGroup = (g: string) => {\n setRequiredGroups((prev) =>\n prev.includes(g) ? prev.filter((x) => x !== g) : [...prev, g],\n );\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n await onSubmit({\n displayName: displayName.trim(),\n slug: slug.trim(),\n subdomain: subdomain.trim(),\n navOrder,\n requiredGroups,\n description: description.trim(),\n });\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"display-name\">Display name</Label>\n <Input\n id=\"display-name\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n placeholder=\"Claims Processing\"\n required\n />\n <p className=\"text-xs text-muted-foreground\">\n Shown in the nav and admin list. Slug and subdomain are auto-derived.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"slug\">Slug</Label>\n <Input\n id=\"slug\"\n value={slug}\n onChange={(e) => {\n setSlugTouched(true);\n setSlug(e.target.value);\n }}\n placeholder=\"claims-processing\"\n required\n pattern=\"^[a-z0-9][a-z0-9-]*$\"\n />\n <p className=\"text-xs text-muted-foreground\">\n Stable identifier (kebab-case). Must match{\" \"}\n <code className=\"font-mono\">APP_SLUG</code> in the app's .env.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"subdomain\">Subdomain</Label>\n <Input\n id=\"subdomain\"\n value={subdomain}\n onChange={(e) => {\n setSubdomainTouched(true);\n setSubdomain(e.target.value);\n }}\n placeholder=\"claims-processing\"\n pattern=\"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$\"\n />\n <p className=\"text-xs text-muted-foreground\">\n Leave empty for the apex app. Otherwise this becomes{\" \"}\n <code className=\"font-mono\">&lt;subdomain&gt;.&lt;apex&gt;</code>.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"nav-order\">Nav order</Label>\n <Input\n id=\"nav-order\"\n type=\"number\"\n value={navOrder}\n onChange={(e) => setNavOrder(Number(e.target.value))}\n />\n <p className=\"text-xs text-muted-foreground\">Lower numbers come first.</p>\n </div>\n\n <div className=\"space-y-2\">\n <Label>Required groups</Label>\n <div className=\"flex flex-wrap gap-2\">\n {availableGroups.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">No groups available.</p>\n ) : (\n availableGroups.map((g) => {\n const checked = requiredGroups.includes(g);\n return (\n <button\n type=\"button\"\n key={g}\n onClick={() => toggleGroup(g)}\n className={\n checked\n ? \"rounded-full border border-primary bg-primary px-3 py-1 text-xs text-primary-foreground\"\n : \"rounded-full border border-border px-3 py-1 text-xs\"\n }\n >\n {g}\n </button>\n );\n })\n )}\n </div>\n <p className=\"text-xs text-muted-foreground\">\n Empty = any authenticated user can see this app.\n </p>\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"description\">Description</Label>\n <Textarea\n id=\"description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n placeholder=\"Optional notes shown in the admin UI.\"\n />\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={busy}>\n {busy ? \"Saving...\" : submitLabel}\n </Button>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
@@ -34,6 +34,7 @@ __export(AppShell_exports, {
34
34
  module.exports = __toCommonJS(AppShell_exports);
35
35
  var import_jsx_runtime = require("react/jsx-runtime");
36
36
  var React = __toESM(require("react"));
37
+ var import_react_query = require("@tanstack/react-query");
37
38
  var import_lucide_react = require("lucide-react");
38
39
  var import_Logo = require("../chrome/Logo.js");
39
40
  var import_ThemeSwitcher = require("../chrome/ThemeSwitcher.js");
@@ -62,16 +63,59 @@ function NavGroup({ group, pathname }) {
62
63
  }) })
63
64
  ] });
64
65
  }
66
+ function EcosystemSection() {
67
+ const currentHostname = typeof window !== "undefined" ? window.location.hostname : "";
68
+ const { data: apps } = (0, import_react_query.useQuery)({
69
+ queryKey: ["augmenting-integrations/registry", "apps"],
70
+ queryFn: async () => {
71
+ const r = await fetch("/api/apps", { credentials: "same-origin" });
72
+ if (!r.ok) return [];
73
+ const json = await r.json();
74
+ return Array.isArray(json) ? json : [];
75
+ },
76
+ staleTime: 5 * 6e4,
77
+ refetchOnWindowFocus: false
78
+ });
79
+ if (!apps || apps.length === 0) return null;
80
+ const sorted = [...apps].sort((a, b) => (a.navOrder ?? 0) - (b.navOrder ?? 0));
81
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1", children: [
82
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Apps" }),
83
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "space-y-0.5", children: sorted.map((app) => {
84
+ let active;
85
+ try {
86
+ active = new URL(app.appUrl).hostname === currentHostname;
87
+ } catch {
88
+ active = false;
89
+ }
90
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
91
+ "a",
92
+ {
93
+ href: app.appUrl,
94
+ className: (0, import_utils.cn)(
95
+ "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition",
96
+ active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/60"
97
+ ),
98
+ children: [
99
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.AppWindow, { className: "h-4 w-4 shrink-0" }),
100
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: app.displayName })
101
+ ]
102
+ }
103
+ ) }, app.slug);
104
+ }) })
105
+ ] });
106
+ }
65
107
  function AppShell({
66
108
  children,
67
109
  pathname,
68
110
  navGroups,
69
111
  homeHref = "/",
70
- showRoleSwitcher = true
112
+ showRoleSwitcher = true,
113
+ showEcosystemNav = true
71
114
  }) {
72
115
  const [mobileOpen, setMobileOpen] = React.useState(false);
73
116
  const sidebarContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex h-full flex-col gap-6 px-3 py-4", children: [
74
117
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: homeHref, className: "px-2", "aria-label": "Home", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Logo.Logo, {}) }),
118
+ showEcosystemNav ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EcosystemSection, {}) : null,
75
119
  navGroups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavGroup, { group: g, pathname }, g.label))
76
120
  ] });
77
121
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-h-svh bg-background text-foreground", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/shells/AppShell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Menu, X } from \"lucide-react\";\nimport { Logo } from \"../chrome/Logo.js\";\nimport { ThemeSwitcher } from \"../chrome/ThemeSwitcher.js\";\nimport { RoleSwitcher } from \"../chrome/RoleSwitcher.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n// Nav uses raw `<a>` not `next/link`. Consumers may set Next.js `basePath`\n// (e.g. /commissions in a path-routed product app), and `<Link>` would\n// silently prepend that prefix to every cross-app href, sending users to\n// /commissions/dashboard etc. which 404s. Raw `<a>` honors the literal\n// href. We lose prefetch for AppShell links, but cross-app navigation\n// crosses Lambda origins anyway, so prefetch wouldn't help.\n\nexport type AppNavItem = {\n href: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n /** When provided, item only renders if the consumer's `getFlag(flag)` returns true. */\n flag?: string;\n};\n\nexport type AppNavGroup = { label: string; items: AppNavItem[] };\n\nfunction NavGroup({ group, pathname }: { group: AppNavGroup; pathname: string }) {\n if (group.items.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <p className=\"px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {group.label}\n </p>\n <ul className=\"space-y-0.5\">\n {group.items.map((item) => {\n const active = pathname === item.href || pathname.startsWith(`${item.href}/`);\n return (\n <li key={item.href}>\n <a\n href={item.href}\n className={cn(\n \"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition\",\n active\n ? \"bg-sidebar-accent text-sidebar-accent-foreground\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent/60\",\n )}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n <span>{item.label}</span>\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\nexport function AppShell({\n children,\n pathname,\n navGroups,\n homeHref = \"/\",\n showRoleSwitcher = true,\n}: {\n children: React.ReactNode;\n pathname: string;\n navGroups: AppNavGroup[];\n homeHref?: string;\n showRoleSwitcher?: boolean;\n}) {\n const [mobileOpen, setMobileOpen] = React.useState(false);\n\n const sidebarContent = (\n <div className=\"flex h-full flex-col gap-6 px-3 py-4\">\n <a href={homeHref} className=\"px-2\" aria-label=\"Home\">\n <Logo />\n </a>\n {navGroups.map((g) => (\n <NavGroup key={g.label} group={g} pathname={pathname} />\n ))}\n </div>\n );\n\n return (\n <div className=\"flex min-h-svh bg-background text-foreground\">\n <aside className=\"hidden w-64 shrink-0 border-r border-sidebar-border bg-sidebar text-sidebar-foreground md:block\">\n {sidebarContent}\n </aside>\n\n {mobileOpen ? (\n <div className=\"fixed inset-0 z-50 flex md:hidden\">\n <div\n className=\"flex-1 bg-foreground/40 backdrop-blur-sm\"\n onClick={() => setMobileOpen(false)}\n aria-hidden\n />\n <aside className=\"relative w-72 border-l border-sidebar-border bg-sidebar text-sidebar-foreground\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(false)}\n aria-label=\"Close menu\"\n className=\"absolute right-2 top-2 inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-sidebar-accent\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n {sidebarContent}\n </aside>\n </div>\n ) : null}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <header className=\"flex h-14 items-center justify-between gap-3 border-b border-border bg-background/80 px-4 backdrop-blur md:px-6\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(true)}\n aria-label=\"Open menu\"\n className=\"inline-flex h-9 w-9 items-center justify-center rounded-md border border-border md:hidden\"\n >\n <Menu className=\"h-4 w-4\" />\n </button>\n <div className=\"flex items-center gap-3 md:ml-auto\">\n {showRoleSwitcher ? <RoleSwitcher /> : null}\n <ThemeSwitcher />\n </div>\n </header>\n <main className=\"min-w-0 flex-1 px-4 py-6 md:px-8 md:py-8\">{children}</main>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BM;AA5BN,YAAuB;AACvB,0BAAwB;AACxB,kBAAqB;AACrB,2BAA8B;AAC9B,0BAA6B;AAC7B,mBAAmB;AAmBnB,SAAS,SAAS,EAAE,OAAO,SAAS,GAA6C;AAC/E,MAAI,MAAM,MAAM,WAAW,EAAG,QAAO;AACrC,SACE,6CAAC,SAAI,WAAU,aACb;AAAA,gDAAC,OAAE,WAAU,4EACV,gBAAM,OACT;AAAA,IACA,4CAAC,QAAG,WAAU,eACX,gBAAM,MAAM,IAAI,CAAC,SAAS;AACzB,YAAM,SAAS,aAAa,KAAK,QAAQ,SAAS,WAAW,GAAG,KAAK,IAAI,GAAG;AAC5E,aACE,4CAAC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,KAAK;AAAA,UACX,eAAW;AAAA,YACT;AAAA,YACA,SACI,qDACA;AAAA,UACN;AAAA,UAEA;AAAA,wDAAC,KAAK,MAAL,EAAU,WAAU,oBAAmB;AAAA,YACxC,4CAAC,UAAM,eAAK,OAAM;AAAA;AAAA;AAAA,MACpB,KAZO,KAAK,IAad;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;AAEO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,mBAAmB;AACrB,GAMG;AACD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAExD,QAAM,iBACJ,6CAAC,SAAI,WAAU,wCACb;AAAA,gDAAC,OAAE,MAAM,UAAU,WAAU,QAAO,cAAW,QAC7C,sDAAC,oBAAK,GACR;AAAA,IACC,UAAU,IAAI,CAAC,MACd,4CAAC,YAAuB,OAAO,GAAG,YAAnB,EAAE,KAAqC,CACvD;AAAA,KACH;AAGF,SACE,6CAAC,SAAI,WAAU,gDACb;AAAA,gDAAC,WAAM,WAAU,mGACd,0BACH;AAAA,IAEC,aACC,6CAAC,SAAI,WAAU,qCACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS,MAAM,cAAc,KAAK;AAAA,UAClC,eAAW;AAAA;AAAA,MACb;AAAA,MACA,6CAAC,WAAM,WAAU,mFACf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,cAAc,KAAK;AAAA,YAClC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,sDAAC,yBAAE,WAAU,WAAU;AAAA;AAAA,QACzB;AAAA,QACC;AAAA,SACH;AAAA,OACF,IACE;AAAA,IAEJ,6CAAC,SAAI,WAAU,gCACb;AAAA,mDAAC,YAAO,WAAU,mHAChB;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,cAAc,IAAI;AAAA,YACjC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,sDAAC,4BAAK,WAAU,WAAU;AAAA;AAAA,QAC5B;AAAA,QACA,6CAAC,SAAI,WAAU,sCACZ;AAAA,6BAAmB,4CAAC,oCAAa,IAAK;AAAA,UACvC,4CAAC,sCAAc;AAAA,WACjB;AAAA,SACF;AAAA,MACA,4CAAC,UAAK,WAAU,4CAA4C,UAAS;AAAA,OACvE;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/shells/AppShell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { Menu, X, AppWindow } from \"lucide-react\";\nimport { Logo } from \"../chrome/Logo.js\";\nimport { ThemeSwitcher } from \"../chrome/ThemeSwitcher.js\";\nimport { RoleSwitcher } from \"../chrome/RoleSwitcher.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n// Nav uses raw `<a>` not `next/link`. Cross-app links target different\n// subdomains (different Lambda origins), and within a single app\n// navigating via raw <a> avoids subtle next/link basePath issues. We\n// lose prefetch but it doesn't help across subdomains anyway.\n\nexport type AppNavItem = {\n href: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n /** When provided, item only renders if the consumer's `getFlag(flag)` returns true. */\n flag?: string;\n};\n\nexport type AppNavGroup = { label: string; items: AppNavItem[] };\n\n/** Subset of @augmenting-integrations/registry's AppRegistryItem that AppShell needs. */\nexport type EcosystemApp = {\n slug: string;\n displayName: string;\n appUrl: string;\n navOrder: number;\n};\n\nfunction NavGroup({ group, pathname }: { group: AppNavGroup; pathname: string }) {\n if (group.items.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <p className=\"px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {group.label}\n </p>\n <ul className=\"space-y-0.5\">\n {group.items.map((item) => {\n const active = pathname === item.href || pathname.startsWith(`${item.href}/`);\n return (\n <li key={item.href}>\n <a\n href={item.href}\n className={cn(\n \"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition\",\n active\n ? \"bg-sidebar-accent text-sidebar-accent-foreground\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent/60\",\n )}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n <span>{item.label}</span>\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\nfunction EcosystemSection() {\n const currentHostname = typeof window !== \"undefined\" ? window.location.hostname : \"\";\n\n const { data: apps } = useQuery<EcosystemApp[]>({\n queryKey: [\"augmenting-integrations/registry\", \"apps\"],\n queryFn: async () => {\n const r = await fetch(\"/api/apps\", { credentials: \"same-origin\" });\n if (!r.ok) return [];\n const json = (await r.json()) as unknown;\n return Array.isArray(json) ? (json as EcosystemApp[]) : [];\n },\n staleTime: 5 * 60_000,\n refetchOnWindowFocus: false,\n });\n\n if (!apps || apps.length === 0) return null;\n\n const sorted = [...apps].sort((a, b) => (a.navOrder ?? 0) - (b.navOrder ?? 0));\n\n return (\n <div className=\"space-y-1\">\n <p className=\"px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n Apps\n </p>\n <ul className=\"space-y-0.5\">\n {sorted.map((app) => {\n let active: boolean;\n try {\n active = new URL(app.appUrl).hostname === currentHostname;\n } catch {\n active = false;\n }\n return (\n <li key={app.slug}>\n <a\n href={app.appUrl}\n className={cn(\n \"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition\",\n active\n ? \"bg-sidebar-accent text-sidebar-accent-foreground\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent/60\",\n )}\n >\n <AppWindow className=\"h-4 w-4 shrink-0\" />\n <span>{app.displayName}</span>\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\nexport function AppShell({\n children,\n pathname,\n navGroups,\n homeHref = \"/\",\n showRoleSwitcher = true,\n showEcosystemNav = true,\n}: {\n children: React.ReactNode;\n pathname: string;\n navGroups: AppNavGroup[];\n homeHref?: string;\n showRoleSwitcher?: boolean;\n /**\n * Show the auto-discovered Apps section that fetches from `/api/apps`.\n * Default true. Set false if the consuming app handles ecosystem nav\n * separately or is deployed standalone.\n */\n showEcosystemNav?: boolean;\n}) {\n const [mobileOpen, setMobileOpen] = React.useState(false);\n\n const sidebarContent = (\n <div className=\"flex h-full flex-col gap-6 px-3 py-4\">\n <a href={homeHref} className=\"px-2\" aria-label=\"Home\">\n <Logo />\n </a>\n {showEcosystemNav ? <EcosystemSection /> : null}\n {navGroups.map((g) => (\n <NavGroup key={g.label} group={g} pathname={pathname} />\n ))}\n </div>\n );\n\n return (\n <div className=\"flex min-h-svh bg-background text-foreground\">\n <aside className=\"hidden w-64 shrink-0 border-r border-sidebar-border bg-sidebar text-sidebar-foreground md:block\">\n {sidebarContent}\n </aside>\n\n {mobileOpen ? (\n <div className=\"fixed inset-0 z-50 flex md:hidden\">\n <div\n className=\"flex-1 bg-foreground/40 backdrop-blur-sm\"\n onClick={() => setMobileOpen(false)}\n aria-hidden\n />\n <aside className=\"relative w-72 border-l border-sidebar-border bg-sidebar text-sidebar-foreground\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(false)}\n aria-label=\"Close menu\"\n className=\"absolute right-2 top-2 inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-sidebar-accent\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n {sidebarContent}\n </aside>\n </div>\n ) : null}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <header className=\"flex h-14 items-center justify-between gap-3 border-b border-border bg-background/80 px-4 backdrop-blur md:px-6\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(true)}\n aria-label=\"Open menu\"\n className=\"inline-flex h-9 w-9 items-center justify-center rounded-md border border-border md:hidden\"\n >\n <Menu className=\"h-4 w-4\" />\n </button>\n <div className=\"flex items-center gap-3 md:ml-auto\">\n {showRoleSwitcher ? <RoleSwitcher /> : null}\n <ThemeSwitcher />\n </div>\n </header>\n <main className=\"min-w-0 flex-1 px-4 py-6 md:px-8 md:py-8\">{children}</main>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -12,11 +12,24 @@ export type AppNavGroup = {
12
12
  label: string;
13
13
  items: AppNavItem[];
14
14
  };
15
- export declare function AppShell({ children, pathname, navGroups, homeHref, showRoleSwitcher, }: {
15
+ /** Subset of @augmenting-integrations/registry's AppRegistryItem that AppShell needs. */
16
+ export type EcosystemApp = {
17
+ slug: string;
18
+ displayName: string;
19
+ appUrl: string;
20
+ navOrder: number;
21
+ };
22
+ export declare function AppShell({ children, pathname, navGroups, homeHref, showRoleSwitcher, showEcosystemNav, }: {
16
23
  children: React.ReactNode;
17
24
  pathname: string;
18
25
  navGroups: AppNavGroup[];
19
26
  homeHref?: string;
20
27
  showRoleSwitcher?: boolean;
28
+ /**
29
+ * Show the auto-discovered Apps section that fetches from `/api/apps`.
30
+ * Default true. Set false if the consuming app handles ecosystem nav
31
+ * separately or is deployed standalone.
32
+ */
33
+ showEcosystemNav?: boolean;
21
34
  }): React.JSX.Element;
22
35
  //# sourceMappingURL=AppShell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../../src/components/shells/AppShell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAkCjE,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAc,EACd,gBAAuB,GACxB,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,qBA4DA"}
1
+ {"version":3,"file":"AppShell.d.ts","sourceRoot":"","sources":["../../../src/components/shells/AppShell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAEjE,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAwFF,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAc,EACd,gBAAuB,EACvB,gBAAuB,GACxB,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,qBA6DA"}
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { Menu, X } from "lucide-react";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { Menu, X, AppWindow } from "lucide-react";
5
6
  import { Logo } from "../chrome/Logo.js";
6
7
  import { ThemeSwitcher } from "../chrome/ThemeSwitcher.js";
7
8
  import { RoleSwitcher } from "../chrome/RoleSwitcher.js";
@@ -29,16 +30,59 @@ function NavGroup({ group, pathname }) {
29
30
  }) })
30
31
  ] });
31
32
  }
33
+ function EcosystemSection() {
34
+ const currentHostname = typeof window !== "undefined" ? window.location.hostname : "";
35
+ const { data: apps } = useQuery({
36
+ queryKey: ["augmenting-integrations/registry", "apps"],
37
+ queryFn: async () => {
38
+ const r = await fetch("/api/apps", { credentials: "same-origin" });
39
+ if (!r.ok) return [];
40
+ const json = await r.json();
41
+ return Array.isArray(json) ? json : [];
42
+ },
43
+ staleTime: 5 * 6e4,
44
+ refetchOnWindowFocus: false
45
+ });
46
+ if (!apps || apps.length === 0) return null;
47
+ const sorted = [...apps].sort((a, b) => (a.navOrder ?? 0) - (b.navOrder ?? 0));
48
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
49
+ /* @__PURE__ */ jsx("p", { className: "px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: "Apps" }),
50
+ /* @__PURE__ */ jsx("ul", { className: "space-y-0.5", children: sorted.map((app) => {
51
+ let active;
52
+ try {
53
+ active = new URL(app.appUrl).hostname === currentHostname;
54
+ } catch {
55
+ active = false;
56
+ }
57
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
58
+ "a",
59
+ {
60
+ href: app.appUrl,
61
+ className: cn(
62
+ "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition",
63
+ active ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent/60"
64
+ ),
65
+ children: [
66
+ /* @__PURE__ */ jsx(AppWindow, { className: "h-4 w-4 shrink-0" }),
67
+ /* @__PURE__ */ jsx("span", { children: app.displayName })
68
+ ]
69
+ }
70
+ ) }, app.slug);
71
+ }) })
72
+ ] });
73
+ }
32
74
  function AppShell({
33
75
  children,
34
76
  pathname,
35
77
  navGroups,
36
78
  homeHref = "/",
37
- showRoleSwitcher = true
79
+ showRoleSwitcher = true,
80
+ showEcosystemNav = true
38
81
  }) {
39
82
  const [mobileOpen, setMobileOpen] = React.useState(false);
40
83
  const sidebarContent = /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-6 px-3 py-4", children: [
41
84
  /* @__PURE__ */ jsx("a", { href: homeHref, className: "px-2", "aria-label": "Home", children: /* @__PURE__ */ jsx(Logo, {}) }),
85
+ showEcosystemNav ? /* @__PURE__ */ jsx(EcosystemSection, {}) : null,
42
86
  navGroups.map((g) => /* @__PURE__ */ jsx(NavGroup, { group: g, pathname }, g.label))
43
87
  ] });
44
88
  return /* @__PURE__ */ jsxs("div", { className: "flex min-h-svh bg-background text-foreground", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/shells/AppShell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { Menu, X } from \"lucide-react\";\nimport { Logo } from \"../chrome/Logo.js\";\nimport { ThemeSwitcher } from \"../chrome/ThemeSwitcher.js\";\nimport { RoleSwitcher } from \"../chrome/RoleSwitcher.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n// Nav uses raw `<a>` not `next/link`. Consumers may set Next.js `basePath`\n// (e.g. /commissions in a path-routed product app), and `<Link>` would\n// silently prepend that prefix to every cross-app href, sending users to\n// /commissions/dashboard etc. which 404s. Raw `<a>` honors the literal\n// href. We lose prefetch for AppShell links, but cross-app navigation\n// crosses Lambda origins anyway, so prefetch wouldn't help.\n\nexport type AppNavItem = {\n href: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n /** When provided, item only renders if the consumer's `getFlag(flag)` returns true. */\n flag?: string;\n};\n\nexport type AppNavGroup = { label: string; items: AppNavItem[] };\n\nfunction NavGroup({ group, pathname }: { group: AppNavGroup; pathname: string }) {\n if (group.items.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <p className=\"px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {group.label}\n </p>\n <ul className=\"space-y-0.5\">\n {group.items.map((item) => {\n const active = pathname === item.href || pathname.startsWith(`${item.href}/`);\n return (\n <li key={item.href}>\n <a\n href={item.href}\n className={cn(\n \"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition\",\n active\n ? \"bg-sidebar-accent text-sidebar-accent-foreground\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent/60\",\n )}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n <span>{item.label}</span>\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\nexport function AppShell({\n children,\n pathname,\n navGroups,\n homeHref = \"/\",\n showRoleSwitcher = true,\n}: {\n children: React.ReactNode;\n pathname: string;\n navGroups: AppNavGroup[];\n homeHref?: string;\n showRoleSwitcher?: boolean;\n}) {\n const [mobileOpen, setMobileOpen] = React.useState(false);\n\n const sidebarContent = (\n <div className=\"flex h-full flex-col gap-6 px-3 py-4\">\n <a href={homeHref} className=\"px-2\" aria-label=\"Home\">\n <Logo />\n </a>\n {navGroups.map((g) => (\n <NavGroup key={g.label} group={g} pathname={pathname} />\n ))}\n </div>\n );\n\n return (\n <div className=\"flex min-h-svh bg-background text-foreground\">\n <aside className=\"hidden w-64 shrink-0 border-r border-sidebar-border bg-sidebar text-sidebar-foreground md:block\">\n {sidebarContent}\n </aside>\n\n {mobileOpen ? (\n <div className=\"fixed inset-0 z-50 flex md:hidden\">\n <div\n className=\"flex-1 bg-foreground/40 backdrop-blur-sm\"\n onClick={() => setMobileOpen(false)}\n aria-hidden\n />\n <aside className=\"relative w-72 border-l border-sidebar-border bg-sidebar text-sidebar-foreground\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(false)}\n aria-label=\"Close menu\"\n className=\"absolute right-2 top-2 inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-sidebar-accent\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n {sidebarContent}\n </aside>\n </div>\n ) : null}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <header className=\"flex h-14 items-center justify-between gap-3 border-b border-border bg-background/80 px-4 backdrop-blur md:px-6\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(true)}\n aria-label=\"Open menu\"\n className=\"inline-flex h-9 w-9 items-center justify-center rounded-md border border-border md:hidden\"\n >\n <Menu className=\"h-4 w-4\" />\n </button>\n <div className=\"flex items-center gap-3 md:ml-auto\">\n {showRoleSwitcher ? <RoleSwitcher /> : null}\n <ThemeSwitcher />\n </div>\n </header>\n <main className=\"min-w-0 flex-1 px-4 py-6 md:px-8 md:py-8\">{children}</main>\n </div>\n </div>\n );\n}\n"],"mappings":";AA8BM,cAQQ,YARR;AA5BN,YAAY,WAAW;AACvB,SAAS,MAAM,SAAS;AACxB,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,UAAU;AAmBnB,SAAS,SAAS,EAAE,OAAO,SAAS,GAA6C;AAC/E,MAAI,MAAM,MAAM,WAAW,EAAG,QAAO;AACrC,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,OAAE,WAAU,4EACV,gBAAM,OACT;AAAA,IACA,oBAAC,QAAG,WAAU,eACX,gBAAM,MAAM,IAAI,CAAC,SAAS;AACzB,YAAM,SAAS,aAAa,KAAK,QAAQ,SAAS,WAAW,GAAG,KAAK,IAAI,GAAG;AAC5E,aACE,oBAAC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,KAAK;AAAA,UACX,WAAW;AAAA,YACT;AAAA,YACA,SACI,qDACA;AAAA,UACN;AAAA,UAEA;AAAA,gCAAC,KAAK,MAAL,EAAU,WAAU,oBAAmB;AAAA,YACxC,oBAAC,UAAM,eAAK,OAAM;AAAA;AAAA;AAAA,MACpB,KAZO,KAAK,IAad;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;AAEO,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,mBAAmB;AACrB,GAMG;AACD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAExD,QAAM,iBACJ,qBAAC,SAAI,WAAU,wCACb;AAAA,wBAAC,OAAE,MAAM,UAAU,WAAU,QAAO,cAAW,QAC7C,8BAAC,QAAK,GACR;AAAA,IACC,UAAU,IAAI,CAAC,MACd,oBAAC,YAAuB,OAAO,GAAG,YAAnB,EAAE,KAAqC,CACvD;AAAA,KACH;AAGF,SACE,qBAAC,SAAI,WAAU,gDACb;AAAA,wBAAC,WAAM,WAAU,mGACd,0BACH;AAAA,IAEC,aACC,qBAAC,SAAI,WAAU,qCACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS,MAAM,cAAc,KAAK;AAAA,UAClC,eAAW;AAAA;AAAA,MACb;AAAA,MACA,qBAAC,WAAM,WAAU,mFACf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,cAAc,KAAK;AAAA,YAClC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,8BAAC,KAAE,WAAU,WAAU;AAAA;AAAA,QACzB;AAAA,QACC;AAAA,SACH;AAAA,OACF,IACE;AAAA,IAEJ,qBAAC,SAAI,WAAU,gCACb;AAAA,2BAAC,YAAO,WAAU,mHAChB;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,cAAc,IAAI;AAAA,YACjC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,8BAAC,QAAK,WAAU,WAAU;AAAA;AAAA,QAC5B;AAAA,QACA,qBAAC,SAAI,WAAU,sCACZ;AAAA,6BAAmB,oBAAC,gBAAa,IAAK;AAAA,UACvC,oBAAC,iBAAc;AAAA,WACjB;AAAA,SACF;AAAA,MACA,oBAAC,UAAK,WAAU,4CAA4C,UAAS;AAAA,OACvE;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/shells/AppShell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { Menu, X, AppWindow } from \"lucide-react\";\nimport { Logo } from \"../chrome/Logo.js\";\nimport { ThemeSwitcher } from \"../chrome/ThemeSwitcher.js\";\nimport { RoleSwitcher } from \"../chrome/RoleSwitcher.js\";\nimport { cn } from \"../../lib/utils.js\";\n\n// Nav uses raw `<a>` not `next/link`. Cross-app links target different\n// subdomains (different Lambda origins), and within a single app\n// navigating via raw <a> avoids subtle next/link basePath issues. We\n// lose prefetch but it doesn't help across subdomains anyway.\n\nexport type AppNavItem = {\n href: string;\n label: string;\n icon: React.ComponentType<{ className?: string }>;\n /** When provided, item only renders if the consumer's `getFlag(flag)` returns true. */\n flag?: string;\n};\n\nexport type AppNavGroup = { label: string; items: AppNavItem[] };\n\n/** Subset of @augmenting-integrations/registry's AppRegistryItem that AppShell needs. */\nexport type EcosystemApp = {\n slug: string;\n displayName: string;\n appUrl: string;\n navOrder: number;\n};\n\nfunction NavGroup({ group, pathname }: { group: AppNavGroup; pathname: string }) {\n if (group.items.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <p className=\"px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n {group.label}\n </p>\n <ul className=\"space-y-0.5\">\n {group.items.map((item) => {\n const active = pathname === item.href || pathname.startsWith(`${item.href}/`);\n return (\n <li key={item.href}>\n <a\n href={item.href}\n className={cn(\n \"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition\",\n active\n ? \"bg-sidebar-accent text-sidebar-accent-foreground\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent/60\",\n )}\n >\n <item.icon className=\"h-4 w-4 shrink-0\" />\n <span>{item.label}</span>\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\nfunction EcosystemSection() {\n const currentHostname = typeof window !== \"undefined\" ? window.location.hostname : \"\";\n\n const { data: apps } = useQuery<EcosystemApp[]>({\n queryKey: [\"augmenting-integrations/registry\", \"apps\"],\n queryFn: async () => {\n const r = await fetch(\"/api/apps\", { credentials: \"same-origin\" });\n if (!r.ok) return [];\n const json = (await r.json()) as unknown;\n return Array.isArray(json) ? (json as EcosystemApp[]) : [];\n },\n staleTime: 5 * 60_000,\n refetchOnWindowFocus: false,\n });\n\n if (!apps || apps.length === 0) return null;\n\n const sorted = [...apps].sort((a, b) => (a.navOrder ?? 0) - (b.navOrder ?? 0));\n\n return (\n <div className=\"space-y-1\">\n <p className=\"px-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n Apps\n </p>\n <ul className=\"space-y-0.5\">\n {sorted.map((app) => {\n let active: boolean;\n try {\n active = new URL(app.appUrl).hostname === currentHostname;\n } catch {\n active = false;\n }\n return (\n <li key={app.slug}>\n <a\n href={app.appUrl}\n className={cn(\n \"flex items-center gap-3 rounded-md px-3 py-2 text-sm transition\",\n active\n ? \"bg-sidebar-accent text-sidebar-accent-foreground\"\n : \"text-sidebar-foreground hover:bg-sidebar-accent/60\",\n )}\n >\n <AppWindow className=\"h-4 w-4 shrink-0\" />\n <span>{app.displayName}</span>\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\nexport function AppShell({\n children,\n pathname,\n navGroups,\n homeHref = \"/\",\n showRoleSwitcher = true,\n showEcosystemNav = true,\n}: {\n children: React.ReactNode;\n pathname: string;\n navGroups: AppNavGroup[];\n homeHref?: string;\n showRoleSwitcher?: boolean;\n /**\n * Show the auto-discovered Apps section that fetches from `/api/apps`.\n * Default true. Set false if the consuming app handles ecosystem nav\n * separately or is deployed standalone.\n */\n showEcosystemNav?: boolean;\n}) {\n const [mobileOpen, setMobileOpen] = React.useState(false);\n\n const sidebarContent = (\n <div className=\"flex h-full flex-col gap-6 px-3 py-4\">\n <a href={homeHref} className=\"px-2\" aria-label=\"Home\">\n <Logo />\n </a>\n {showEcosystemNav ? <EcosystemSection /> : null}\n {navGroups.map((g) => (\n <NavGroup key={g.label} group={g} pathname={pathname} />\n ))}\n </div>\n );\n\n return (\n <div className=\"flex min-h-svh bg-background text-foreground\">\n <aside className=\"hidden w-64 shrink-0 border-r border-sidebar-border bg-sidebar text-sidebar-foreground md:block\">\n {sidebarContent}\n </aside>\n\n {mobileOpen ? (\n <div className=\"fixed inset-0 z-50 flex md:hidden\">\n <div\n className=\"flex-1 bg-foreground/40 backdrop-blur-sm\"\n onClick={() => setMobileOpen(false)}\n aria-hidden\n />\n <aside className=\"relative w-72 border-l border-sidebar-border bg-sidebar text-sidebar-foreground\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(false)}\n aria-label=\"Close menu\"\n className=\"absolute right-2 top-2 inline-flex h-9 w-9 items-center justify-center rounded-md hover:bg-sidebar-accent\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n {sidebarContent}\n </aside>\n </div>\n ) : null}\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <header className=\"flex h-14 items-center justify-between gap-3 border-b border-border bg-background/80 px-4 backdrop-blur md:px-6\">\n <button\n type=\"button\"\n onClick={() => setMobileOpen(true)}\n aria-label=\"Open menu\"\n className=\"inline-flex h-9 w-9 items-center justify-center rounded-md border border-border md:hidden\"\n >\n <Menu className=\"h-4 w-4\" />\n </button>\n <div className=\"flex items-center gap-3 md:ml-auto\">\n {showRoleSwitcher ? <RoleSwitcher /> : null}\n <ThemeSwitcher />\n </div>\n </header>\n <main className=\"min-w-0 flex-1 px-4 py-6 md:px-8 md:py-8\">{children}</main>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -19,6 +19,7 @@ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "defau
19
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
20
  var index_exports = {};
21
21
  __export(index_exports, {
22
+ AppRegistryForm: () => import_AppRegistryForm.AppRegistryForm,
22
23
  AppShell: () => import_AppShell.AppShell,
23
24
  Hero: () => import_Hero.Hero,
24
25
  Logo: () => import_Logo.Logo,
@@ -58,6 +59,7 @@ var import_MdxContent = require("./components/marketing/MdxContent.js");
58
59
  var import_PublicShell = require("./components/shells/PublicShell.js");
59
60
  var import_AppShell = require("./components/shells/AppShell.js");
60
61
  var import_StudioShell = require("./components/shells/StudioShell.js");
62
+ var import_AppRegistryForm = require("./components/admin/AppRegistryForm.js");
61
63
  var import_SessionProvider = require("./providers/SessionProvider.js");
62
64
  var import_QueryProvider = require("./providers/QueryProvider.js");
63
65
  var import_ThemeProvider = require("./providers/ThemeProvider.js");
@@ -65,6 +67,7 @@ var import_MockProvider = require("./providers/MockProvider.js");
65
67
  var import_utils = require("./lib/utils.js");
66
68
  // Annotate the CommonJS export names for ESM import in node:
67
69
  0 && (module.exports = {
70
+ AppRegistryForm,
68
71
  AppShell,
69
72
  Hero,
70
73
  Logo,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// shadcn/ui components\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\nexport * from \"./components/ui/textarea.js\";\n\n// Chrome\nexport { Logo } from \"./components/chrome/Logo.js\";\nexport { ThemeSwitcher } from \"./components/chrome/ThemeSwitcher.js\";\nexport { RoleSwitcher } from \"./components/chrome/RoleSwitcher.js\";\n\n// Marketing\nexport { Hero } from \"./components/marketing/Hero.js\";\nexport { MdxContent } from \"./components/marketing/MdxContent.js\";\n\n// Shells\nexport { PublicShell } from \"./components/shells/PublicShell.js\";\nexport type { NavLink } from \"./components/shells/PublicShell.js\";\nexport { AppShell } from \"./components/shells/AppShell.js\";\nexport type { AppNavItem, AppNavGroup } from \"./components/shells/AppShell.js\";\nexport { StudioShell } from \"./components/shells/StudioShell.js\";\n\n// Providers\nexport { SessionProvider } from \"./providers/SessionProvider.js\";\nexport { QueryProvider } from \"./providers/QueryProvider.js\";\nexport { ThemeProvider, useThemeName } from \"./providers/ThemeProvider.js\";\nexport { MockProvider } from \"./providers/MockProvider.js\";\n\n// Helpers\nexport { cn } from \"./lib/utils.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,0BAAc,sCADd;AAEA,0BAAc,qCAFd;AAGA,0BAAc,sCAHd;AAIA,0BAAc,oCAJd;AAKA,0BAAc,wCALd;AAMA,0BAAc,sCANd;AAOA,0BAAc,6CAPd;AAQA,0BAAc,qCARd;AASA,0BAAc,qCATd;AAUA,0BAAc,sCAVd;AAWA,0BAAc,yCAXd;AAYA,0BAAc,qCAZd;AAaA,0BAAc,qCAbd;AAcA,0BAAc,oCAdd;AAeA,0BAAc,wCAfd;AAkBA,kBAAqB;AACrB,2BAA8B;AAC9B,0BAA6B;AAG7B,kBAAqB;AACrB,wBAA2B;AAG3B,yBAA4B;AAE5B,sBAAyB;AAEzB,yBAA4B;AAG5B,6BAAgC;AAChC,2BAA8B;AAC9B,2BAA4C;AAC5C,0BAA6B;AAG7B,mBAAmB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// shadcn/ui components\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\nexport * from \"./components/ui/textarea.js\";\n\n// Chrome\nexport { Logo } from \"./components/chrome/Logo.js\";\nexport { ThemeSwitcher } from \"./components/chrome/ThemeSwitcher.js\";\nexport { RoleSwitcher } from \"./components/chrome/RoleSwitcher.js\";\n\n// Marketing\nexport { Hero } from \"./components/marketing/Hero.js\";\nexport { MdxContent } from \"./components/marketing/MdxContent.js\";\n\n// Shells\nexport { PublicShell } from \"./components/shells/PublicShell.js\";\nexport type { NavLink } from \"./components/shells/PublicShell.js\";\nexport { AppShell } from \"./components/shells/AppShell.js\";\nexport type {\n AppNavItem,\n AppNavGroup,\n EcosystemApp,\n} from \"./components/shells/AppShell.js\";\nexport { StudioShell } from \"./components/shells/StudioShell.js\";\n\n// Admin\nexport { AppRegistryForm } from \"./components/admin/AppRegistryForm.js\";\nexport type {\n AppRegistryFormValue,\n AppRegistryFormProps,\n} from \"./components/admin/AppRegistryForm.js\";\n\n// Providers\nexport { SessionProvider } from \"./providers/SessionProvider.js\";\nexport { QueryProvider } from \"./providers/QueryProvider.js\";\nexport { ThemeProvider, useThemeName } from \"./providers/ThemeProvider.js\";\nexport { MockProvider } from \"./providers/MockProvider.js\";\n\n// Helpers\nexport { cn } from \"./lib/utils.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,0BAAc,sCADd;AAEA,0BAAc,qCAFd;AAGA,0BAAc,sCAHd;AAIA,0BAAc,oCAJd;AAKA,0BAAc,wCALd;AAMA,0BAAc,sCANd;AAOA,0BAAc,6CAPd;AAQA,0BAAc,qCARd;AASA,0BAAc,qCATd;AAUA,0BAAc,sCAVd;AAWA,0BAAc,yCAXd;AAYA,0BAAc,qCAZd;AAaA,0BAAc,qCAbd;AAcA,0BAAc,oCAdd;AAeA,0BAAc,wCAfd;AAkBA,kBAAqB;AACrB,2BAA8B;AAC9B,0BAA6B;AAG7B,kBAAqB;AACrB,wBAA2B;AAG3B,yBAA4B;AAE5B,sBAAyB;AAMzB,yBAA4B;AAG5B,6BAAgC;AAOhC,6BAAgC;AAChC,2BAA8B;AAC9B,2BAA4C;AAC5C,0BAA6B;AAG7B,mBAAmB;","names":[]}
package/dist/index.d.ts CHANGED
@@ -21,8 +21,10 @@ export { MdxContent } from "./components/marketing/MdxContent.js";
21
21
  export { PublicShell } from "./components/shells/PublicShell.js";
22
22
  export type { NavLink } from "./components/shells/PublicShell.js";
23
23
  export { AppShell } from "./components/shells/AppShell.js";
24
- export type { AppNavItem, AppNavGroup } from "./components/shells/AppShell.js";
24
+ export type { AppNavItem, AppNavGroup, EcosystemApp, } from "./components/shells/AppShell.js";
25
25
  export { StudioShell } from "./components/shells/StudioShell.js";
26
+ export { AppRegistryForm } from "./components/admin/AppRegistryForm.js";
27
+ export type { AppRegistryFormValue, AppRegistryFormProps, } from "./components/admin/AppRegistryForm.js";
26
28
  export { SessionProvider } from "./providers/SessionProvider.js";
27
29
  export { QueryProvider } from "./providers/QueryProvider.js";
28
30
  export { ThemeProvider, useThemeName } from "./providers/ThemeProvider.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAG5C,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAGnE,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,YAAY,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAG5C,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAGnE,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,YAAY,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,YAAY,EACV,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAGjE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,YAAY,EACV,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,uCAAuC,CAAC;AAG/C,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -21,12 +21,14 @@ import { MdxContent } from "./components/marketing/MdxContent.js";
21
21
  import { PublicShell } from "./components/shells/PublicShell.js";
22
22
  import { AppShell } from "./components/shells/AppShell.js";
23
23
  import { StudioShell } from "./components/shells/StudioShell.js";
24
+ import { AppRegistryForm } from "./components/admin/AppRegistryForm.js";
24
25
  import { SessionProvider } from "./providers/SessionProvider.js";
25
26
  import { QueryProvider } from "./providers/QueryProvider.js";
26
27
  import { ThemeProvider, useThemeName } from "./providers/ThemeProvider.js";
27
28
  import { MockProvider } from "./providers/MockProvider.js";
28
29
  import { cn } from "./lib/utils.js";
29
30
  export {
31
+ AppRegistryForm,
30
32
  AppShell,
31
33
  Hero,
32
34
  Logo,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// shadcn/ui components\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\nexport * from \"./components/ui/textarea.js\";\n\n// Chrome\nexport { Logo } from \"./components/chrome/Logo.js\";\nexport { ThemeSwitcher } from \"./components/chrome/ThemeSwitcher.js\";\nexport { RoleSwitcher } from \"./components/chrome/RoleSwitcher.js\";\n\n// Marketing\nexport { Hero } from \"./components/marketing/Hero.js\";\nexport { MdxContent } from \"./components/marketing/MdxContent.js\";\n\n// Shells\nexport { PublicShell } from \"./components/shells/PublicShell.js\";\nexport type { NavLink } from \"./components/shells/PublicShell.js\";\nexport { AppShell } from \"./components/shells/AppShell.js\";\nexport type { AppNavItem, AppNavGroup } from \"./components/shells/AppShell.js\";\nexport { StudioShell } from \"./components/shells/StudioShell.js\";\n\n// Providers\nexport { SessionProvider } from \"./providers/SessionProvider.js\";\nexport { QueryProvider } from \"./providers/QueryProvider.js\";\nexport { ThemeProvider, useThemeName } from \"./providers/ThemeProvider.js\";\nexport { MockProvider } from \"./providers/MockProvider.js\";\n\n// Helpers\nexport { cn } from \"./lib/utils.js\";\n"],"mappings":"AACA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAG7B,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAG3B,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAEzB,SAAS,mBAAmB;AAG5B,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAG7B,SAAS,UAAU;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// shadcn/ui components\nexport * from \"./components/ui/avatar.js\";\nexport * from \"./components/ui/badge.js\";\nexport * from \"./components/ui/button.js\";\nexport * from \"./components/ui/card.js\";\nexport * from \"./components/ui/checkbox.js\";\nexport * from \"./components/ui/dialog.js\";\nexport * from \"./components/ui/dropdown-menu.js\";\nexport * from \"./components/ui/input.js\";\nexport * from \"./components/ui/label.js\";\nexport * from \"./components/ui/select.js\";\nexport * from \"./components/ui/separator.js\";\nexport * from \"./components/ui/sheet.js\";\nexport * from \"./components/ui/table.js\";\nexport * from \"./components/ui/tabs.js\";\nexport * from \"./components/ui/textarea.js\";\n\n// Chrome\nexport { Logo } from \"./components/chrome/Logo.js\";\nexport { ThemeSwitcher } from \"./components/chrome/ThemeSwitcher.js\";\nexport { RoleSwitcher } from \"./components/chrome/RoleSwitcher.js\";\n\n// Marketing\nexport { Hero } from \"./components/marketing/Hero.js\";\nexport { MdxContent } from \"./components/marketing/MdxContent.js\";\n\n// Shells\nexport { PublicShell } from \"./components/shells/PublicShell.js\";\nexport type { NavLink } from \"./components/shells/PublicShell.js\";\nexport { AppShell } from \"./components/shells/AppShell.js\";\nexport type {\n AppNavItem,\n AppNavGroup,\n EcosystemApp,\n} from \"./components/shells/AppShell.js\";\nexport { StudioShell } from \"./components/shells/StudioShell.js\";\n\n// Admin\nexport { AppRegistryForm } from \"./components/admin/AppRegistryForm.js\";\nexport type {\n AppRegistryFormValue,\n AppRegistryFormProps,\n} from \"./components/admin/AppRegistryForm.js\";\n\n// Providers\nexport { SessionProvider } from \"./providers/SessionProvider.js\";\nexport { QueryProvider } from \"./providers/QueryProvider.js\";\nexport { ThemeProvider, useThemeName } from \"./providers/ThemeProvider.js\";\nexport { MockProvider } from \"./providers/MockProvider.js\";\n\n// Helpers\nexport { cn } from \"./lib/utils.js\";\n"],"mappings":"AACA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,SAAS,YAAY;AACrB,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAG7B,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAG3B,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAMzB,SAAS,mBAAmB;AAG5B,SAAS,uBAAuB;AAOhC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAG7B,SAAS,UAAU;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augmenting-integrations/ui",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "Shared UI: shadcn components, brand chrome (Logo, ThemeSwitcher, RoleSwitcher), shells (PublicShell, AppShell, StudioShell), and providers (Session, Query, Theme, Mock).",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -21,6 +21,11 @@
21
21
  "dist",
22
22
  "README.md"
23
23
  ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "clean": "rm -rf dist",
27
+ "test": "vitest run --passWithNoTests"
28
+ },
24
29
  "dependencies": {
25
30
  "class-variance-authority": "^0.7.1",
26
31
  "clsx": "^2.1.1",
@@ -31,17 +36,19 @@
31
36
  "tailwind-merge": "^3.5.0"
32
37
  },
33
38
  "peerDependencies": {
39
+ "@augmenting-integrations/brand": "workspace:*",
40
+ "@augmenting-integrations/themes": "workspace:*",
34
41
  "@tanstack/react-query": "^5.0.0",
35
42
  "msw": "^2.0.0",
36
43
  "next": "^16.0.0",
37
44
  "next-auth": "^5.0.0-beta.31",
38
45
  "next-themes": "^0.4.0",
39
46
  "react": "^19.0.0",
40
- "react-dom": "^19.0.0",
41
- "@augmenting-integrations/themes": "4.0.2",
42
- "@augmenting-integrations/brand": "4.0.2"
47
+ "react-dom": "^19.0.0"
43
48
  },
44
49
  "devDependencies": {
50
+ "@augmenting-integrations/brand": "workspace:*",
51
+ "@augmenting-integrations/themes": "workspace:*",
45
52
  "@tanstack/react-query": "^5.100.9",
46
53
  "@types/react": "^19.0.0",
47
54
  "@types/react-dom": "^19.0.0",
@@ -53,13 +60,6 @@
53
60
  "react-dom": "^19.0.0",
54
61
  "tsup": "^8.3.5",
55
62
  "typescript": "^5.7.2",
56
- "vitest": "^4.1.5",
57
- "@augmenting-integrations/brand": "4.0.2",
58
- "@augmenting-integrations/themes": "4.0.2"
59
- },
60
- "scripts": {
61
- "build": "tsup",
62
- "clean": "rm -rf dist",
63
- "test": "vitest run --passWithNoTests"
63
+ "vitest": "^4.1.5"
64
64
  }
65
- }
65
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Augmenting Integrations LLC
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.