@protobase/ui 0.51.3 → 0.52.1
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/dist/account/blocks/connected-apps-block.js +15 -3
- package/dist/account/connector-dialog.js +14 -0
- package/dist/account/mcp-address-section.js +45 -0
- package/dist/account/messages.js +10 -2
- package/dist/app/app.js +6 -16
- package/dist/auth/auth-layout.d.ts +4 -2
- package/dist/auth/auth-layout.js +7 -3
- package/dist/auth/auth-provider.js +2 -2
- package/dist/auth/sign-in-prompt.js +4 -4
- package/dist/connect/connect-page.js +38 -35
- package/dist/connect/connect-route.js +37 -0
- package/dist/notifications/bell/messages.js +0 -2
- package/dist/notifications/bell/notification-item.js +2 -10
- package/dist/notifications/bell/notification-target.js +1 -1
- package/dist/package.js +1 -1
- package/dist/shell/assets/{app-BiL2dM7x.js → app-DT9luXUb.js} +8 -8
- package/dist/shell/assets/index-CymocR26.css +2 -0
- package/dist/shell/assets/{index-DnaLvHu6.js → index-DnjfXnJH.js} +2 -2
- package/dist/shell/assets/{records-map-CvVnB3Ev.js → records-map-2ZUhf18Z.js} +2 -2
- package/dist/shell/assets/{shared-protobase-ui-CTj1OZ6i.js → shared-protobase-ui-4S2lZi18.js} +2 -2
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-DT9luXUb.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-DnaLvHu6.js.map → index-DnjfXnJH.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-CvVnB3Ev.js.map → records-map-2ZUhf18Z.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-CTj1OZ6i.js.map → shared-protobase-ui-4S2lZi18.js.map} +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/index-DETGvbIP.css +0 -2
- package/dist/shell-source-maps/assets/app-BiL2dM7x.js.map +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { text } from "../messages.js";
|
|
2
2
|
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { useAdminMeta } from "../../app/meta-gate.js";
|
|
3
4
|
import { AccountLoading } from "./account-loading.js";
|
|
4
5
|
import { useAccountData } from "./use-account-data.js";
|
|
5
6
|
import { ConnectedAppsSection } from "../connected-apps-section.js";
|
|
7
|
+
import { McpAddressSection } from "../mcp-address-section.js";
|
|
6
8
|
import { useCallback } from "react";
|
|
7
|
-
import { jsx } from "react/jsx-runtime";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
10
|
//#region src/account/blocks/connected-apps-block.tsx
|
|
9
11
|
const Apps = () => {
|
|
10
12
|
const { account } = useAuth().session;
|
|
@@ -15,7 +17,17 @@ const Apps = () => {
|
|
|
15
17
|
onDisconnect: (clientId) => account.disconnectApp(clientId).then(reload)
|
|
16
18
|
});
|
|
17
19
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const Mcp = ({ url }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(McpAddressSection, {
|
|
21
|
+
url,
|
|
22
|
+
appName: useAdminMeta().appName
|
|
23
|
+
}), /* @__PURE__ */ jsx(Apps, {})] });
|
|
24
|
+
/**
|
|
25
|
+
* The `ConnectedApps` block: the address of the app's MCP endpoint and the apps the signed-in person allowed to act as
|
|
26
|
+
* them, hidden when the app has no MCP.
|
|
27
|
+
*/
|
|
28
|
+
const ConnectedAppsBlock = () => {
|
|
29
|
+
const { mcp } = useAuth();
|
|
30
|
+
return mcp ? /* @__PURE__ */ jsx(Mcp, { url: mcp.url }) : null;
|
|
31
|
+
};
|
|
20
32
|
//#endregion
|
|
21
33
|
export { ConnectedAppsBlock };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/account/connector-dialog.ts
|
|
2
|
+
/**
|
|
3
|
+
* The address of Claude's dialog that adds a custom connector, filled in with the MCP endpoint's `url` and, when the
|
|
4
|
+
* app has one, its name; the person sees both there and confirms.
|
|
5
|
+
*/
|
|
6
|
+
const connectorDialogUrl = (url, appName) => {
|
|
7
|
+
const dialog = new URL("https://claude.ai/customize/connectors");
|
|
8
|
+
dialog.searchParams.set("modal", "add-custom-connector");
|
|
9
|
+
if (appName) dialog.searchParams.set("connectorName", appName);
|
|
10
|
+
dialog.searchParams.set("connectorUrl", url);
|
|
11
|
+
return dialog.href;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { connectorDialogUrl };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
2
|
+
import { buttonClass } from "../primitives/button.js";
|
|
3
|
+
import { AccountSection } from "./account-section.js";
|
|
4
|
+
import { connectorDialogUrl } from "./connector-dialog.js";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { Check, Copy, ExternalLink } from "lucide-react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/mcp-address-section.tsx
|
|
9
|
+
/** The address of the app's MCP endpoint, to copy into an AI client or add to Claude, with how connecting goes. */
|
|
10
|
+
const McpAddressSection = ({ url, appName }) => {
|
|
11
|
+
const [copied, setCopied] = useState(false);
|
|
12
|
+
return /* @__PURE__ */ jsx(AccountSection, {
|
|
13
|
+
title: text("mcpAddress"),
|
|
14
|
+
description: text("mcpAddressDescription"),
|
|
15
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
16
|
+
className: "flex flex-wrap items-center gap-2",
|
|
17
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
18
|
+
className: "flex min-w-0 flex-1 items-center gap-2 rounded-md border bg-surface py-1.5 pl-3 pr-1.5",
|
|
19
|
+
children: [/* @__PURE__ */ jsx("code", {
|
|
20
|
+
className: "min-w-0 flex-1 break-all font-mono text-xs",
|
|
21
|
+
children: url
|
|
22
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
23
|
+
type: "button",
|
|
24
|
+
"aria-label": copied ? text("copied") : text("copyMcpAddress"),
|
|
25
|
+
onClick: () => {
|
|
26
|
+
navigator.clipboard.writeText(url).then(() => {
|
|
27
|
+
setCopied(true);
|
|
28
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
className: "inline-flex size-8 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted pointer-coarse:size-11",
|
|
32
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "size-4 text-success" }) : /* @__PURE__ */ jsx(Copy, { className: "size-4" })
|
|
33
|
+
})]
|
|
34
|
+
}), /* @__PURE__ */ jsxs("a", {
|
|
35
|
+
href: connectorDialogUrl(url, appName),
|
|
36
|
+
target: "_blank",
|
|
37
|
+
rel: "noreferrer",
|
|
38
|
+
className: buttonClass(),
|
|
39
|
+
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "size-3.5" }), text("addToClaude")]
|
|
40
|
+
})]
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { McpAddressSection };
|
package/dist/account/messages.js
CHANGED
|
@@ -149,7 +149,11 @@ const text = defineMessages({
|
|
|
149
149
|
couldNotReadStaffSignIns: "Could not read the staff sign-ins",
|
|
150
150
|
couldNotReadPolicy: "Could not read the sign-in policy",
|
|
151
151
|
policySaved: "Saved. It applies to every sign-in from now on.",
|
|
152
|
-
policySubtitle: "How people sign in to this app. Signed-in people keep their session, and are asked for what is newly required within 15 minutes."
|
|
152
|
+
policySubtitle: "How people sign in to this app. Signed-in people keep their session, and are asked for what is newly required within 15 minutes.",
|
|
153
|
+
mcpAddress: "MCP address",
|
|
154
|
+
mcpAddressDescription: "Add this address to an AI assistant or code editor that speaks MCP, as a custom connector or remote MCP server. It opens this app in your browser, where you sign in and allow it; it then works with your access.",
|
|
155
|
+
copyMcpAddress: "Copy the MCP address",
|
|
156
|
+
addToClaude: "Add to Claude"
|
|
153
157
|
},
|
|
154
158
|
nl: {
|
|
155
159
|
account: "Account",
|
|
@@ -299,7 +303,11 @@ const text = defineMessages({
|
|
|
299
303
|
couldNotReadStaffSignIns: "Kon de inlogs van medewerkers niet lezen",
|
|
300
304
|
couldNotReadPolicy: "Kon het inlogbeleid niet lezen",
|
|
301
305
|
policySaved: "Bewaard. Het geldt vanaf nu voor elke inlog.",
|
|
302
|
-
policySubtitle: "Hoe mensen inloggen bij deze app. Wie is ingelogd, houdt de sessie en krijgt binnen 15 minuten de vraag naar wat nieuw verplicht is."
|
|
306
|
+
policySubtitle: "Hoe mensen inloggen bij deze app. Wie is ingelogd, houdt de sessie en krijgt binnen 15 minuten de vraag naar wat nieuw verplicht is.",
|
|
307
|
+
mcpAddress: "MCP-adres",
|
|
308
|
+
mcpAddressDescription: "Voeg dit adres toe aan een AI-assistent of code-editor die MCP spreekt, als aangepaste connector of externe MCP-server. Die opent deze app in je browser, waar je inlogt en hem toestaat; daarna werkt hij met jouw toegang.",
|
|
309
|
+
copyMcpAddress: "Het MCP-adres kopiëren",
|
|
310
|
+
addToClaude: "Toevoegen aan Claude"
|
|
303
311
|
}
|
|
304
312
|
});
|
|
305
313
|
//#endregion
|
package/dist/app/app.js
CHANGED
|
@@ -18,10 +18,10 @@ import { CustomBoundary } from "./pages/custom-boundary.js";
|
|
|
18
18
|
import { useSidebarNoticeShown } from "../app-shell/sidebar-notice.js";
|
|
19
19
|
import { AppShell } from "../app-shell/app-shell.js";
|
|
20
20
|
import { useSignInAddress } from "../auth/use-sign-in-address.js";
|
|
21
|
-
import { SignInPrompt } from "../auth/sign-in-prompt.js";
|
|
22
21
|
import { managesOrganization, organizationMenuItems, organizationPageTitle } from "../organizations/organization-paths.js";
|
|
23
22
|
import { AccountPage, useAccountPage } from "../account/account-page.js";
|
|
24
|
-
import {
|
|
23
|
+
import { documentTitle, titleParts, useDocumentTitle } from "./document-title.js";
|
|
24
|
+
import { ConnectRoute } from "../connect/connect-route.js";
|
|
25
25
|
import { SignInPolicyPage } from "../account/sign-in-policy-page.js";
|
|
26
26
|
import { PluginSourceProvider } from "../plugins/plugin-source.js";
|
|
27
27
|
import { AuthGate } from "../auth/auth-gate.js";
|
|
@@ -29,7 +29,6 @@ import { isAdmin, userToShell } from "../auth/user-to-shell.js";
|
|
|
29
29
|
import { AuthLocale } from "../auth/auth-locale.js";
|
|
30
30
|
import { useLiveUpdates } from "../data/use-live-updates.js";
|
|
31
31
|
import { useOpenedRecord } from "../data/use-record.js";
|
|
32
|
-
import { documentTitle, titleParts, useDocumentTitle } from "./document-title.js";
|
|
33
32
|
import { PublicFormPage } from "../forms/public-form-page.js";
|
|
34
33
|
import { useFormAddress } from "../forms/use-form-address.js";
|
|
35
34
|
import { ThemeProvider, useTheme } from "../theme/theme-provider.js";
|
|
@@ -73,10 +72,6 @@ const placeOf = (address) => {
|
|
|
73
72
|
return address?.resource;
|
|
74
73
|
};
|
|
75
74
|
const recordOf = (address) => address?.kind === "record" ? address : void 0;
|
|
76
|
-
const connectPrompt = {
|
|
77
|
-
title: "Sign in to connect an app",
|
|
78
|
-
description: "An app asks to act as you here. Sign in, then choose whether to allow it."
|
|
79
|
-
};
|
|
80
75
|
const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
81
76
|
const meta = useAdminMeta();
|
|
82
77
|
const { state, session, signOut, stopStaffSession, organizations: organizationSettings } = useAuth();
|
|
@@ -151,7 +146,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
151
146
|
const account = useAccountPage();
|
|
152
147
|
const accountAddress = user ? readAccountPath(path) : void 0;
|
|
153
148
|
const accountTab = accountAddress && account ? shownPageTab(pageTabs(account.tree), accountAddress.tab) : void 0;
|
|
154
|
-
const accountPage =
|
|
149
|
+
const accountPage = !user ? void 0 : accountAddress ? account?.title ?? text("account") : path === "/-/sign-in-policy" ? text("signInPolicy") : organizationPage;
|
|
155
150
|
const resourceLabel = view?.names?.plural ?? (resource && humanize(resource));
|
|
156
151
|
const breadcrumb = accountPage ? [accountPage] : breadcrumbFor({
|
|
157
152
|
basePath,
|
|
@@ -180,13 +175,7 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
180
175
|
recordTitle: title,
|
|
181
176
|
recordLoading: titleLoading
|
|
182
177
|
}), meta.appName));
|
|
183
|
-
const content = path ===
|
|
184
|
-
email: user.email,
|
|
185
|
-
appName: meta.appName ?? "this app"
|
|
186
|
-
}) : /* @__PURE__ */ jsx(SignInPrompt, {
|
|
187
|
-
as: "page",
|
|
188
|
-
prompt: connectPrompt
|
|
189
|
-
}) : accountAddress ? /* @__PURE__ */ jsx(AccountPage, { ...accountAddress }) : user && path === "/-/sign-in-policy" ? admin ? /* @__PURE__ */ jsx(SignInPolicyPage, {}) : /* @__PURE__ */ jsx(Notice, {
|
|
178
|
+
const content = accountAddress ? /* @__PURE__ */ jsx(AccountPage, { ...accountAddress }) : user && path === "/-/sign-in-policy" ? admin ? /* @__PURE__ */ jsx(SignInPolicyPage, {}) : /* @__PURE__ */ jsx(Notice, {
|
|
190
179
|
title: text("adminsOnly"),
|
|
191
180
|
children: text("onlyAdminPolicy")
|
|
192
181
|
}) : organizationPage && path === "/-/organization" ? /* @__PURE__ */ jsx(OrganizationPage, {
|
|
@@ -294,13 +283,14 @@ const Routes = ({ workspace, sidebarMode, assistant, runtime }) => {
|
|
|
294
283
|
const Authenticated = ({ baseUrl, client, sources, workspace, sidebarMode, ui, assistant, runtime }) => {
|
|
295
284
|
const { state, session, markSignedOut } = useAuth();
|
|
296
285
|
const sidebarBanner = useSidebarNoticeShown(sidebarMode ?? "text-small");
|
|
286
|
+
const { path } = useRouter();
|
|
297
287
|
return /* @__PURE__ */ jsx(ApiProvider, {
|
|
298
288
|
baseUrl,
|
|
299
289
|
client,
|
|
300
290
|
sources,
|
|
301
291
|
token: state.kind === "visitor" ? void 0 : session.token,
|
|
302
292
|
onUnauthenticated: markSignedOut,
|
|
303
|
-
children: /* @__PURE__ */ jsx(MetaGate, { children: /* @__PURE__ */ jsxs(ToastProvider, { children: [!sidebarBanner && /* @__PURE__ */ jsx(NewVersionToast, {}), /* @__PURE__ */ jsx(PluginGate, {
|
|
293
|
+
children: /* @__PURE__ */ jsx(MetaGate, { children: /* @__PURE__ */ jsxs(ToastProvider, { children: [!sidebarBanner && /* @__PURE__ */ jsx(NewVersionToast, {}), path === connectRoute ? /* @__PURE__ */ jsx(ConnectRoute, {}) : /* @__PURE__ */ jsx(PluginGate, {
|
|
304
294
|
ui,
|
|
305
295
|
children: /* @__PURE__ */ jsx(Routes, {
|
|
306
296
|
workspace,
|
|
@@ -5,11 +5,13 @@ export type AuthLayoutProps = {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
/** How wide the card is: `sm`, the default, for a few short inputs; `md` for a form with longer ones. */
|
|
7
7
|
size?: 'sm' | 'md';
|
|
8
|
+
/** The path of the app's own icon, shown in place of Protobase's mark. */
|
|
9
|
+
icon?: string;
|
|
8
10
|
children: ReactNode;
|
|
9
11
|
};
|
|
10
12
|
/**
|
|
11
|
-
* The frame of the sign-in and first-run pages and of public forms: logo, title and a card. On a page as short as a
|
|
13
|
+
* The frame of the sign-in and first-run pages and of public forms: logo, or the app's own icon, title and a card. On a page as short as a
|
|
12
14
|
* phone's above its keyboard the logo and most of the margins go, so the card still shows whole above the keyboard.
|
|
13
15
|
*/
|
|
14
|
-
export declare const AuthLayout: ({ title, description, size, children }: AuthLayoutProps) => import("react").JSX.Element;
|
|
16
|
+
export declare const AuthLayout: ({ title, description, size, icon, children }: AuthLayoutProps) => import("react").JSX.Element;
|
|
15
17
|
//#endregion
|
package/dist/auth/auth-layout.js
CHANGED
|
@@ -3,17 +3,21 @@ import { LogoMark } from "../app-shell/logo.js";
|
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/auth/auth-layout.tsx
|
|
5
5
|
/**
|
|
6
|
-
* The frame of the sign-in and first-run pages and of public forms: logo, title and a card. On a page as short as a
|
|
6
|
+
* The frame of the sign-in and first-run pages and of public forms: logo, or the app's own icon, title and a card. On a page as short as a
|
|
7
7
|
* phone's above its keyboard the logo and most of the margins go, so the card still shows whole above the keyboard.
|
|
8
8
|
*/
|
|
9
|
-
const AuthLayout = ({ title, description, size = "sm", children }) => /* @__PURE__ */ jsx("div", {
|
|
9
|
+
const AuthLayout = ({ title, description, size = "sm", icon, children }) => /* @__PURE__ */ jsx("div", {
|
|
10
10
|
className: "flex min-h-full w-full items-center justify-center bg-surface px-4 pb-[calc(2.5rem+var(--pb-safe-bottom))] pt-[calc(2.5rem+var(--pb-safe-top))] short:pb-[calc(1rem+var(--pb-safe-bottom))] short:pt-[calc(1rem+var(--pb-safe-top))]",
|
|
11
11
|
children: /* @__PURE__ */ jsxs("div", {
|
|
12
12
|
className: cn("w-full", size === "sm" ? "max-w-sm" : "max-w-lg"),
|
|
13
13
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
14
14
|
className: "mb-6 flex flex-col items-center text-center short:mb-4",
|
|
15
15
|
children: [
|
|
16
|
-
/* @__PURE__ */ jsx(
|
|
16
|
+
icon ? /* @__PURE__ */ jsx("img", {
|
|
17
|
+
src: icon,
|
|
18
|
+
alt: "",
|
|
19
|
+
className: "size-10 object-contain short:hidden"
|
|
20
|
+
}) : /* @__PURE__ */ jsx(LogoMark, { className: "size-10 short:hidden" }),
|
|
17
21
|
/* @__PURE__ */ jsx("h1", {
|
|
18
22
|
className: "mt-4 text-xl font-semibold tracking-tight short:mt-0",
|
|
19
23
|
children: title
|
|
@@ -25,7 +25,7 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
25
25
|
const [signInFirst, setSignInFirst] = useState("password");
|
|
26
26
|
const [passwordReset, setPasswordReset] = useState(false);
|
|
27
27
|
const [changeEmail, setChangeEmail] = useState(false);
|
|
28
|
-
const [mcp, setMcp] = useState(
|
|
28
|
+
const [mcp, setMcp] = useState();
|
|
29
29
|
const [canSignUp, setCanSignUp] = useState(false);
|
|
30
30
|
const [passkeyDomains, setPasskeyDomains] = useState([]);
|
|
31
31
|
const [socialProviders, setSocialProviders] = useState([]);
|
|
@@ -110,7 +110,7 @@ const AuthProvider = ({ session: given, origin, nativeBridge, children }) => {
|
|
|
110
110
|
setSignInFirst(status.signInFirst ?? "password");
|
|
111
111
|
setPasswordReset(status.passwordReset && !nativeBridge);
|
|
112
112
|
setChangeEmail(Boolean(status.changeEmail));
|
|
113
|
-
setMcp(
|
|
113
|
+
setMcp(status.mcp);
|
|
114
114
|
setCanSignUp(Boolean(status.signUp));
|
|
115
115
|
setPasskeyDomains(status.passkeyDomains ?? []);
|
|
116
116
|
setSocialProviders(status.socialProviders);
|
|
@@ -19,7 +19,7 @@ const Benefits = ({ benefits }) => benefits && benefits.length > 0 ? /* @__PURE_
|
|
|
19
19
|
}), benefit]
|
|
20
20
|
}, benefit))
|
|
21
21
|
}) : null;
|
|
22
|
-
const
|
|
22
|
+
const SignInPromptActions = ({ prompt }) => {
|
|
23
23
|
const { canSignUp } = useAuth();
|
|
24
24
|
const { link } = useSignInAddress();
|
|
25
25
|
const primary = buttonClass({ variant: "primary" });
|
|
@@ -57,7 +57,7 @@ const SignInPrompt = (props) => {
|
|
|
57
57
|
actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
58
58
|
onClick: props.onClose,
|
|
59
59
|
children: text("notNow")
|
|
60
|
-
}), /* @__PURE__ */ jsx(
|
|
60
|
+
}), /* @__PURE__ */ jsx(SignInPromptActions, { prompt })] }),
|
|
61
61
|
children: /* @__PURE__ */ jsx(Benefits, { benefits: prompt.benefits })
|
|
62
62
|
});
|
|
63
63
|
const Icon = namedIcon(prompt.icon) ?? LogIn;
|
|
@@ -86,10 +86,10 @@ const SignInPrompt = (props) => {
|
|
|
86
86
|
/* @__PURE__ */ jsx(Benefits, { benefits: prompt.benefits }),
|
|
87
87
|
/* @__PURE__ */ jsx("div", {
|
|
88
88
|
className: "mt-2 flex w-full flex-col-reverse gap-2 sm:w-auto sm:flex-row sm:justify-center [&>*]:min-h-11 sm:[&>*]:min-h-0",
|
|
89
|
-
children: /* @__PURE__ */ jsx(
|
|
89
|
+
children: /* @__PURE__ */ jsx(SignInPromptActions, { prompt })
|
|
90
90
|
})
|
|
91
91
|
]
|
|
92
92
|
});
|
|
93
93
|
};
|
|
94
94
|
//#endregion
|
|
95
|
-
export { SignInPrompt };
|
|
95
|
+
export { SignInPrompt, SignInPromptActions };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useAuth } from "../auth/auth-provider.js";
|
|
2
2
|
import { Spinner } from "../primitives/spinner.js";
|
|
3
|
-
import { Notice } from "../app/meta-gate.js";
|
|
4
3
|
import { Button } from "../primitives/button.js";
|
|
5
4
|
import { useRouter } from "../app/router.js";
|
|
6
5
|
import { authMessage } from "../auth/auth-messages.js";
|
|
6
|
+
import { AuthLayout } from "../auth/auth-layout.js";
|
|
7
7
|
import { clientName } from "./client-name.js";
|
|
8
8
|
import { clientPublisher } from "./client-publisher.js";
|
|
9
9
|
import { isFramed, useArmedAfterFocus, useGuardedPress } from "./interaction-guard.js";
|
|
@@ -39,7 +39,15 @@ const TargetText = ({ target }) => target.kind === "site" ? /* @__PURE__ */ jsxs
|
|
|
39
39
|
className: "break-all text-foreground",
|
|
40
40
|
children: target.uri
|
|
41
41
|
})] });
|
|
42
|
-
const
|
|
42
|
+
const Notice = ({ title, icon, children }) => /* @__PURE__ */ jsx(AuthLayout, {
|
|
43
|
+
title,
|
|
44
|
+
icon,
|
|
45
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
46
|
+
className: "text-muted-foreground",
|
|
47
|
+
children
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
const Request = ({ asking, appName, appIcon, email, onAnswer, onSignOut }) => {
|
|
43
51
|
const [busy, setBusy] = useState(false);
|
|
44
52
|
const armed = useArmedAfterFocus(500);
|
|
45
53
|
const answer = (accept) => {
|
|
@@ -50,38 +58,28 @@ const Request = ({ asking, appName, email, onAnswer, onSignOut }) => {
|
|
|
50
58
|
const name = clientName(asking.client.client_name);
|
|
51
59
|
const publisher = clientPublisher(asking.client.client_id);
|
|
52
60
|
const { target } = asking;
|
|
53
|
-
return /* @__PURE__ */ jsx(
|
|
54
|
-
|
|
61
|
+
return /* @__PURE__ */ jsx(AuthLayout, {
|
|
62
|
+
title: `Allow an app to act as you in ${appName}?`,
|
|
63
|
+
icon: appIcon,
|
|
64
|
+
size: "md",
|
|
55
65
|
children: /* @__PURE__ */ jsxs("section", {
|
|
56
66
|
"aria-label": "Allow an app",
|
|
57
|
-
className: "rounded-lg border bg-background p-5",
|
|
58
67
|
children: [
|
|
59
|
-
/* @__PURE__ */ jsxs("
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
title: name,
|
|
75
|
-
children: [
|
|
76
|
-
"“",
|
|
77
|
-
name ?? "no name",
|
|
78
|
-
"”"
|
|
79
|
-
]
|
|
80
|
-
}),
|
|
81
|
-
" ",
|
|
82
|
-
"asks to act as you here."
|
|
83
|
-
]
|
|
84
|
-
}),
|
|
68
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
69
|
+
"An app that calls itself",
|
|
70
|
+
" ",
|
|
71
|
+
/* @__PURE__ */ jsxs("bdi", {
|
|
72
|
+
className: "inline-block max-w-full truncate align-bottom font-medium",
|
|
73
|
+
title: name,
|
|
74
|
+
children: [
|
|
75
|
+
"“",
|
|
76
|
+
name ?? "no name",
|
|
77
|
+
"”"
|
|
78
|
+
]
|
|
79
|
+
}),
|
|
80
|
+
" ",
|
|
81
|
+
"asks to act as you here."
|
|
82
|
+
] }),
|
|
85
83
|
publisher && /* @__PURE__ */ jsxs("p", {
|
|
86
84
|
className: "mt-1 text-muted-foreground",
|
|
87
85
|
children: [
|
|
@@ -139,7 +137,7 @@ const Request = ({ asking, appName, email, onAnswer, onSignOut }) => {
|
|
|
139
137
|
})
|
|
140
138
|
});
|
|
141
139
|
};
|
|
142
|
-
const Connecting = ({ oauthQuery, appName, email, assign }) => {
|
|
140
|
+
const Connecting = ({ oauthQuery, appName, appIcon, email, assign }) => {
|
|
143
141
|
const { session, signOut } = useAuth();
|
|
144
142
|
const { path, basePath } = useRouter();
|
|
145
143
|
const go = goWith(assign);
|
|
@@ -185,15 +183,17 @@ const Connecting = ({ oauthQuery, appName, email, assign }) => {
|
|
|
185
183
|
};
|
|
186
184
|
if (state.kind === "failed") return /* @__PURE__ */ jsx(Notice, {
|
|
187
185
|
title: "This app cannot be connected",
|
|
186
|
+
icon: appIcon,
|
|
188
187
|
children: state.message
|
|
189
188
|
});
|
|
190
189
|
if (state.kind === "loading") return /* @__PURE__ */ jsxs("div", {
|
|
191
|
-
className: "flex min-h-
|
|
190
|
+
className: "flex min-h-full w-full items-center justify-center gap-2 bg-surface text-muted-foreground",
|
|
192
191
|
children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
|
|
193
192
|
});
|
|
194
193
|
return /* @__PURE__ */ jsx(Request, {
|
|
195
194
|
asking: state.asking,
|
|
196
195
|
appName,
|
|
196
|
+
appIcon,
|
|
197
197
|
email,
|
|
198
198
|
onAnswer: (accept) => answer(accept, state.asking),
|
|
199
199
|
onSignOut: () => void signOut()
|
|
@@ -201,14 +201,15 @@ const Connecting = ({ oauthQuery, appName, email, assign }) => {
|
|
|
201
201
|
};
|
|
202
202
|
/**
|
|
203
203
|
* The connect page: someone signed in allows an outside app, such as an MCP client, to act as them, from the request
|
|
204
|
-
* the server signed and sent here. Inside another page's frame it shows only that it cannot be used there and calls
|
|
204
|
+
* the server signed and sent here. It stands alone on the page, as the sign-in pages do, without the app's shell. Inside another page's frame it shows only that it cannot be used there and calls
|
|
205
205
|
* nothing, whatever headers the host sent; Allow takes a click only once the page has been in front for a moment.
|
|
206
206
|
*/
|
|
207
|
-
const ConnectPage = ({ email, appName, isFramed: isFramed$1 = isFramed, assign = (url) => window.location.assign(url) }) => {
|
|
207
|
+
const ConnectPage = ({ email, appName, appIcon, isFramed: isFramed$1 = isFramed, assign = (url) => window.location.assign(url) }) => {
|
|
208
208
|
const [framed] = useState(isFramed$1);
|
|
209
209
|
const { oauthQuery } = useConnectAddress();
|
|
210
210
|
if (framed) return /* @__PURE__ */ jsxs(Notice, {
|
|
211
211
|
title: "This page cannot be used inside another site",
|
|
212
|
+
icon: appIcon,
|
|
212
213
|
children: [
|
|
213
214
|
/* @__PURE__ */ jsx("a", {
|
|
214
215
|
href: window.location.href,
|
|
@@ -223,11 +224,13 @@ const ConnectPage = ({ email, appName, isFramed: isFramed$1 = isFramed, assign =
|
|
|
223
224
|
});
|
|
224
225
|
if (!oauthQuery) return /* @__PURE__ */ jsx(Notice, {
|
|
225
226
|
title: "Nothing to connect",
|
|
227
|
+
icon: appIcon,
|
|
226
228
|
children: "Connect again from your app."
|
|
227
229
|
});
|
|
228
230
|
return /* @__PURE__ */ jsx(Connecting, {
|
|
229
231
|
oauthQuery,
|
|
230
232
|
appName,
|
|
233
|
+
appIcon,
|
|
231
234
|
email,
|
|
232
235
|
assign
|
|
233
236
|
}, oauthQuery);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useAuth } from "../auth/auth-provider.js";
|
|
2
|
+
import { useAdminMeta } from "../app/meta-gate.js";
|
|
3
|
+
import { SignInPromptActions } from "../auth/sign-in-prompt.js";
|
|
4
|
+
import { AuthLayout } from "../auth/auth-layout.js";
|
|
5
|
+
import { documentTitle, useDocumentTitle } from "../app/document-title.js";
|
|
6
|
+
import { ConnectPage } from "./connect-page.js";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/connect/connect-route.tsx
|
|
9
|
+
const connectPrompt = {
|
|
10
|
+
title: "Sign in to connect an app",
|
|
11
|
+
description: "An app asks to act as you here. Sign in, then choose whether to allow it."
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* `/-/connect`, alone on the page without the app's shell, as the sign-in pages are: the connect page for someone
|
|
15
|
+
* signed in, else the way to sign in first. Both carry the app's name and icon.
|
|
16
|
+
*/
|
|
17
|
+
const ConnectRoute = () => {
|
|
18
|
+
const meta = useAdminMeta();
|
|
19
|
+
const { state } = useAuth();
|
|
20
|
+
useDocumentTitle(documentTitle(["Connect an app"], meta.appName));
|
|
21
|
+
if (state.kind === "signed-in") return /* @__PURE__ */ jsx(ConnectPage, {
|
|
22
|
+
email: state.user.email,
|
|
23
|
+
appName: meta.appName ?? "this app",
|
|
24
|
+
appIcon: meta.appIcon
|
|
25
|
+
});
|
|
26
|
+
return /* @__PURE__ */ jsx(AuthLayout, {
|
|
27
|
+
title: connectPrompt.title,
|
|
28
|
+
description: connectPrompt.description,
|
|
29
|
+
icon: meta.appIcon,
|
|
30
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
31
|
+
className: "flex flex-col gap-2 [&>*]:min-h-10 [&>*]:w-full",
|
|
32
|
+
children: /* @__PURE__ */ jsx(SignInPromptActions, { prompt: connectPrompt })
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ConnectRoute };
|
|
@@ -11,7 +11,6 @@ const text = defineMessages({
|
|
|
11
11
|
loadFailed: "Your notifications could not be loaded.",
|
|
12
12
|
tryAgain: "Try again",
|
|
13
13
|
unread: "Unread",
|
|
14
|
-
opensNewTab: "(opens in a new tab)",
|
|
15
14
|
pushOff: "Get notifications on this device, even when the app is closed.",
|
|
16
15
|
turnOn: "Turn on",
|
|
17
16
|
pushOn: "Notifications are on for this device.",
|
|
@@ -28,7 +27,6 @@ const text = defineMessages({
|
|
|
28
27
|
loadFailed: "Je meldingen konden niet worden geladen.",
|
|
29
28
|
tryAgain: "Opnieuw proberen",
|
|
30
29
|
unread: "Ongelezen",
|
|
31
|
-
opensNewTab: "(opent in een nieuw tabblad)",
|
|
32
30
|
pushOff: "Ontvang meldingen op dit apparaat, ook als de app gesloten is.",
|
|
33
31
|
turnOn: "Inschakelen",
|
|
34
32
|
pushOn: "Meldingen staan op dit apparaat aan.",
|
|
@@ -8,8 +8,8 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
const row = "flex w-full gap-2.5 px-3 py-2.5 text-left";
|
|
9
9
|
/**
|
|
10
10
|
* One notification of the panel: unread ones in bold beside a dot, its text, and how long ago it came. A notification
|
|
11
|
-
* with a url is a link to it, followed in the app for the app's own pages and
|
|
12
|
-
*
|
|
11
|
+
* with a url is a link to it, followed in the app for the app's own pages and loaded in this tab for any other; one
|
|
12
|
+
* without is not a link. The history never answers a url on another site.
|
|
13
13
|
*/
|
|
14
14
|
const NotificationItem = ({ item, target, onNavigate, onOpen, now }) => {
|
|
15
15
|
const unread = item.readAt === null;
|
|
@@ -34,10 +34,6 @@ const NotificationItem = ({ item, target, onNavigate, onOpen, now }) => {
|
|
|
34
34
|
dateTime: item.createdAt,
|
|
35
35
|
className: "mt-0.5 block text-xs text-faint-foreground",
|
|
36
36
|
children: formatRelativeTime((now - Date.parse(item.createdAt)) / 1e3)
|
|
37
|
-
}),
|
|
38
|
-
target?.kind === "external" && /* @__PURE__ */ jsx("span", {
|
|
39
|
-
className: "sr-only",
|
|
40
|
-
children: text("opensNewTab")
|
|
41
37
|
})
|
|
42
38
|
]
|
|
43
39
|
})] });
|
|
@@ -64,10 +60,6 @@ const NotificationItem = ({ item, target, onNavigate, onOpen, now }) => {
|
|
|
64
60
|
onClick: opened,
|
|
65
61
|
onAuxClick: middle,
|
|
66
62
|
className: cn(row, "hover:bg-muted"),
|
|
67
|
-
...target.kind === "external" && {
|
|
68
|
-
target: "_blank",
|
|
69
|
-
rel: "noopener"
|
|
70
|
-
},
|
|
71
63
|
children: content
|
|
72
64
|
});
|
|
73
65
|
};
|
|
@@ -5,7 +5,7 @@ const notificationTarget = (url, { origin, basePath }) => {
|
|
|
5
5
|
const target = new URL(url, origin);
|
|
6
6
|
if (target.protocol !== "http:" && target.protocol !== "https:") return void 0;
|
|
7
7
|
if (target.origin !== origin) return {
|
|
8
|
-
kind: "
|
|
8
|
+
kind: "page",
|
|
9
9
|
href: target.href
|
|
10
10
|
};
|
|
11
11
|
const local = `${target.pathname}${target.search}${target.hash}`;
|
package/dist/package.js
CHANGED