@agent-native/toolkit 0.216.0 → 0.217.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-native.eject.json +5 -0
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +33 -44
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/onboarding/GettingStartedCard.d.ts +35 -0
- package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
- package/dist/app/onboarding/GettingStartedCard.js +38 -0
- package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
- package/dist/app/onboarding/index.d.ts +1 -0
- package/dist/app/onboarding/index.d.ts.map +1 -1
- package/dist/app/onboarding/index.js +1 -0
- package/dist/app/onboarding/index.js.map +1 -1
- package/dist/app/providers/AppProviders.d.ts.map +1 -1
- package/dist/app/providers/AppProviders.js +3 -1
- package/dist/app/providers/AppProviders.js.map +1 -1
- package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
- package/dist/app/settings/AccountSettingsCard.js +2 -1
- package/dist/app/settings/AccountSettingsCard.js.map +1 -1
- package/dist/app/settings/index.d.ts +1 -0
- package/dist/app/settings/index.d.ts.map +1 -1
- package/dist/app/settings/index.js +1 -0
- package/dist/app/settings/index.js.map +1 -1
- package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
- package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
- package/dist/app/settings/shell/SettingsShell.js +87 -20
- package/dist/app/settings/shell/SettingsShell.js.map +1 -1
- package/dist/app/settings/shell/bridge.d.ts +5 -3
- package/dist/app/settings/shell/bridge.d.ts.map +1 -1
- package/dist/app/settings/shell/bridge.js +8 -5
- package/dist/app/settings/shell/bridge.js.map +1 -1
- package/dist/app/settings/shell/index.d.ts +2 -2
- package/dist/app/settings/shell/index.d.ts.map +1 -1
- package/dist/app/settings/shell/index.js.map +1 -1
- package/dist/app/settings/shell/registry.d.ts +9 -1
- package/dist/app/settings/shell/registry.d.ts.map +1 -1
- package/dist/app/settings/shell/registry.js +16 -0
- package/dist/app/settings/shell/registry.js.map +1 -1
- package/dist/app/settings/shell/routing.d.ts +5 -4
- package/dist/app/settings/shell/routing.d.ts.map +1 -1
- package/dist/app/settings/shell/routing.js +38 -10
- package/dist/app/settings/shell/routing.js.map +1 -1
- package/dist/app/shared/CommandMenu.d.ts +86 -6
- package/dist/app/shared/CommandMenu.d.ts.map +1 -1
- package/dist/app/shared/CommandMenu.js +303 -41
- package/dist/app/shared/CommandMenu.js.map +1 -1
- package/dist/app/shared/ConsentBanner.d.ts +9 -0
- package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
- package/dist/app/shared/ConsentBanner.js +35 -0
- package/dist/app/shared/ConsentBanner.js.map +1 -0
- package/dist/ui/sonner.d.ts +2 -2
- package/dist/ui/sonner.d.ts.map +1 -1
- package/dist/ui/sonner.js +2 -2
- package/dist/ui/sonner.js.map +1 -1
- package/package.json +3 -3
- package/src/app/auth/AuthPage.interaction.spec.tsx +74 -0
- package/src/app/auth/AuthPage.tsx +32 -39
- package/src/app/onboarding/GettingStartedCard.tsx +203 -0
- package/src/app/onboarding/index.ts +1 -0
- package/src/app/providers/AppProviders.tsx +7 -1
- package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
- package/src/app/settings/AccountSettingsCard.tsx +8 -0
- package/src/app/settings/index.ts +1 -0
- package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
- package/src/app/settings/shell/SettingsShell.tsx +245 -31
- package/src/app/settings/shell/bridge.spec.ts +12 -0
- package/src/app/settings/shell/bridge.ts +17 -4
- package/src/app/settings/shell/index.ts +6 -1
- package/src/app/settings/shell/registry.ts +33 -1
- package/src/app/settings/shell/routing.spec.ts +92 -1
- package/src/app/settings/shell/routing.ts +52 -9
- package/src/app/shared/CommandMenu.spec.tsx +1662 -1
- package/src/app/shared/CommandMenu.tsx +763 -164
- package/src/app/shared/ConsentBanner.spec.tsx +141 -0
- package/src/app/shared/ConsentBanner.tsx +141 -0
- package/src/ui/sonner.spec.ts +10 -0
- package/src/ui/sonner.tsx +2 -2
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { getConsentChoice, resolveConsentRegion, setConsentChoice, shouldShowConsentBanner, subscribeConsent, } from "@agent-native/core/client/consent";
|
|
3
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
+
import { Button } from "@agent-native/toolkit/ui/button";
|
|
5
|
+
import { Label } from "@agent-native/toolkit/ui/label";
|
|
6
|
+
import { Switch } from "@agent-native/toolkit/ui/switch";
|
|
7
|
+
import { useEffect, useState, useSyncExternalStore } from "react";
|
|
8
|
+
const DEFAULT_PRIVACY_POLICY_HREF = "https://www.agent-native.com/privacy";
|
|
9
|
+
/**
|
|
10
|
+
* The cookie banner for visitors in the consent region. Renders nothing
|
|
11
|
+
* elsewhere, and nothing once the visitor has chosen, until
|
|
12
|
+
* `openConsentSettings()` reopens it.
|
|
13
|
+
*/
|
|
14
|
+
export function ConsentBanner({ privacyPolicyHref = DEFAULT_PRIVACY_POLICY_HREF, }) {
|
|
15
|
+
const t = useT();
|
|
16
|
+
const open = useSyncExternalStore(subscribeConsent, shouldShowConsentBanner, () => false);
|
|
17
|
+
const [managing, setManaging] = useState(false);
|
|
18
|
+
const [performance, setPerformance] = useState(false);
|
|
19
|
+
const [targeting, setTargeting] = useState(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
void resolveConsentRegion();
|
|
22
|
+
}, []);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!open)
|
|
25
|
+
return;
|
|
26
|
+
const stored = getConsentChoice();
|
|
27
|
+
setPerformance(stored?.performance ?? false);
|
|
28
|
+
setTargeting(stored?.targeting ?? false);
|
|
29
|
+
setManaging(false);
|
|
30
|
+
}, [open]);
|
|
31
|
+
if (!open)
|
|
32
|
+
return null;
|
|
33
|
+
return (_jsxs("section", { role: "region", "aria-label": t("consentBanner.label"), "data-agent-native-consent-banner": true, className: "fixed bottom-4 left-4 z-[100] flex w-[min(420px,calc(100vw-2rem))] flex-col gap-3 rounded-lg border bg-popover p-4 text-sm text-popover-foreground shadow-lg", children: [_jsxs("p", { className: "m-0 leading-relaxed", children: [t("consentBanner.message"), " ", _jsx("a", { href: privacyPolicyHref, target: "_blank", rel: "noreferrer", className: "underline underline-offset-2", children: t("consentBanner.privacyPolicy") })] }), managing ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "an-consent-necessary", children: t("consentBanner.necessary") }), _jsx(Switch, { id: "an-consent-necessary", checked: true, disabled: true })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "an-consent-performance", children: t("consentBanner.performance") }), _jsx(Switch, { id: "an-consent-performance", checked: performance, onCheckedChange: setPerformance })] }), _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx(Label, { htmlFor: "an-consent-targeting", children: t("consentBanner.targeting") }), _jsx(Switch, { id: "an-consent-targeting", checked: targeting, onCheckedChange: setTargeting })] })] })) : null, _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", className: "flex-1", onClick: () => setConsentChoice({ performance: false, targeting: false }), children: t("consentBanner.rejectAll") }), _jsx(Button, { variant: "outline", size: "sm", className: "flex-1", onClick: () => setConsentChoice({ performance: true, targeting: true }), children: t("consentBanner.acceptAll") }), managing ? (_jsx(Button, { variant: "outline", size: "sm", className: "flex-1", onClick: () => setConsentChoice({ performance, targeting }), children: t("consentBanner.save") })) : (_jsx(Button, { variant: "ghost", size: "sm", className: "flex-1", onClick: () => setManaging(true), children: t("consentBanner.manage") }))] })] }));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=ConsentBanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConsentBanner.js","sourceRoot":"","sources":["../../../src/app/shared/ConsentBanner.tsx"],"names":[],"mappings":"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"}
|
package/dist/ui/sonner.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Toaster as Sonner } from "sonner";
|
|
1
|
+
import { toast as sonnerToast, Toaster as Sonner } from "sonner";
|
|
2
2
|
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
|
3
3
|
declare const Toaster: ({ className, toastOptions, ...props }: ToasterProps) => import("react").JSX.Element;
|
|
4
|
-
export { Toaster };
|
|
4
|
+
export { Toaster, sonnerToast };
|
|
5
5
|
//# sourceMappingURL=sonner.d.ts.map
|
package/dist/ui/sonner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sonner.d.ts","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"sonner.d.ts","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIjE,KAAK,YAAY,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,CAAC;AASxD,QAAA,MAAM,OAAO,0CAA2C,YAAY,gCAyCnE,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/ui/sonner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useTheme } from "next-themes";
|
|
3
|
-
import { Toaster as Sonner } from "sonner";
|
|
3
|
+
import { toast as sonnerToast, Toaster as Sonner } from "sonner";
|
|
4
4
|
import { cn } from "../utils.js";
|
|
5
5
|
const toastWidthClasses = "group-[.toaster]:!w-[var(--width)] group-[.toaster]:!min-w-[min(20rem,calc(100vw_-_2rem))] group-[.toaster]:!max-w-[var(--width)] group-[.toaster]:!gap-3 group-[.toaster]:!break-normal";
|
|
6
6
|
const toastContentClasses = "group-[.toast]:!min-w-0 group-[.toast]:!flex-1 group-[.toast]:!basis-auto group-[.toast]:break-words";
|
|
@@ -21,5 +21,5 @@ const Toaster = ({ className, toastOptions, ...props }) => {
|
|
|
21
21
|
},
|
|
22
22
|
}, ...props }));
|
|
23
23
|
};
|
|
24
|
-
export { Toaster };
|
|
24
|
+
export { Toaster, sonnerToast };
|
|
25
25
|
//# sourceMappingURL=sonner.js.map
|
package/dist/ui/sonner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sonner.js","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"sonner.js","sourceRoot":"","sources":["../../src/ui/sonner.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEjE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAIjC,MAAM,iBAAiB,GACrB,0LAA0L,CAAC;AAC7L,MAAM,mBAAmB,GACvB,sGAAsG,CAAC;AACzG,MAAM,kBAAkB,GACtB,4DAA4D,CAAC;AAE/D,MAAM,OAAO,GAAG,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,KAAK,EAAgB,EAAE,EAAE;IACtE,MAAM,EAAE,KAAK,GAAG,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAAC;IACxC,MAAM,UAAU,GAAG,YAAY,EAAE,UAAU,CAAC;IAE5C,OAAO,CACL,KAAC,MAAM,IACL,KAAK,EAAE,KAA8B,EACrC,SAAS,EAAE,EAAE,CACX,uDAAuD,EACvD,SAAS,CACV,EACD,YAAY,EAAE;YACZ,GAAG,YAAY;YACf,UAAU,EAAE;gBACV,GAAG,UAAU;gBACb,KAAK,EAAE,EAAE,CACP,iBAAiB,EACjB,uIAAuI,EACvI,UAAU,EAAE,KAAK,CAClB;gBACD,KAAK,EAAE,EAAE,CAAC,4BAA4B,EAAE,UAAU,EAAE,KAAK,CAAC;gBAC1D,WAAW,EAAE,EAAE,CACb,iEAAiE,EACjE,UAAU,EAAE,WAAW,CACxB;gBACD,OAAO,EAAE,EAAE,CAAC,mBAAmB,EAAE,UAAU,EAAE,OAAO,CAAC;gBACrD,YAAY,EAAE,EAAE,CACd,kBAAkB,EAClB,kEAAkE,EAClE,UAAU,EAAE,YAAY,CACzB;gBACD,YAAY,EAAE,EAAE,CACd,kBAAkB,EAClB,8DAA8D,EAC9D,UAAU,EAAE,YAAY,CACzB;aACF;SACF,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.217.0",
|
|
4
4
|
"description": "Reusable app-building UI and helpers for Agent-Native apps.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -678,7 +678,7 @@
|
|
|
678
678
|
"y-protocols": "^1.0.7",
|
|
679
679
|
"yjs": "^13.6.33",
|
|
680
680
|
"zod": "^4.3.6",
|
|
681
|
-
"@agent-native/agentkit": "^0.
|
|
681
|
+
"@agent-native/agentkit": "^0.217.0"
|
|
682
682
|
},
|
|
683
683
|
"devDependencies": {
|
|
684
684
|
"@excalidraw/excalidraw": "0.18.1",
|
|
@@ -702,7 +702,7 @@
|
|
|
702
702
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
703
703
|
"vite": "8.1.0",
|
|
704
704
|
"vitest": "^4.1.5",
|
|
705
|
-
"@agent-native/core": "^0.
|
|
705
|
+
"@agent-native/core": "^0.217.0"
|
|
706
706
|
},
|
|
707
707
|
"peerDependencies": {
|
|
708
708
|
"@agent-native/core": ">=0.205.1",
|
|
@@ -247,6 +247,80 @@ describe("AuthPage interactions", () => {
|
|
|
247
247
|
}
|
|
248
248
|
});
|
|
249
249
|
|
|
250
|
+
it("stores no visitor id on the sign-in page before analytics consent", async () => {
|
|
251
|
+
const originalHref = window.location.href;
|
|
252
|
+
const sendBeacon = vi.fn(() => true);
|
|
253
|
+
Object.defineProperty(navigator, "sendBeacon", {
|
|
254
|
+
value: sendBeacon,
|
|
255
|
+
configurable: true,
|
|
256
|
+
});
|
|
257
|
+
window.happyDOM.setURL("https://clips.agent-native.com/signup");
|
|
258
|
+
// Earlier renders in this file already settled the region.
|
|
259
|
+
Reflect.deleteProperty(
|
|
260
|
+
globalThis,
|
|
261
|
+
Symbol.for("agent-native.client.consent"),
|
|
262
|
+
);
|
|
263
|
+
window.localStorage.removeItem("agent-native.anonymous_id");
|
|
264
|
+
document.cookie = "an_aid=; path=/; max-age=0";
|
|
265
|
+
window.sessionStorage.setItem("an_consent_region", "consent");
|
|
266
|
+
(
|
|
267
|
+
window as Window & { __AGENT_NATIVE_CONFIG__?: Record<string, string> }
|
|
268
|
+
).__AGENT_NATIVE_CONFIG__ = {
|
|
269
|
+
agentNativeAnalyticsPublicKey: "anpk_test",
|
|
270
|
+
agentNativeAnalyticsEndpoint: "https://analytics.agent-native.com/track",
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
await act(async () => {
|
|
275
|
+
root.render(
|
|
276
|
+
<AuthPage
|
|
277
|
+
{...propsFromHtml(
|
|
278
|
+
getOnboardingHtml({
|
|
279
|
+
requestHost: "clips.agent-native.com",
|
|
280
|
+
requestPath: "/signup",
|
|
281
|
+
}),
|
|
282
|
+
)}
|
|
283
|
+
initialView="signup"
|
|
284
|
+
/>,
|
|
285
|
+
);
|
|
286
|
+
await Promise.resolve();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const email = container.querySelector<HTMLInputElement>("#s-email");
|
|
290
|
+
await act(async () => {
|
|
291
|
+
Object.getOwnPropertyDescriptor(
|
|
292
|
+
HTMLInputElement.prototype,
|
|
293
|
+
"value",
|
|
294
|
+
)!.set!.call(email, "person@example.com");
|
|
295
|
+
email!.dispatchEvent(new Event("input", { bubbles: true }));
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(sendBeacon).toHaveBeenCalled();
|
|
299
|
+
for (const [, body] of sendBeacon.mock.calls as unknown as [
|
|
300
|
+
string,
|
|
301
|
+
string,
|
|
302
|
+
][]) {
|
|
303
|
+
const sent = JSON.parse(body);
|
|
304
|
+
expect(sent.anonymousId).toBeUndefined();
|
|
305
|
+
expect(sent.properties.analytics_consent).toBe("withheld");
|
|
306
|
+
}
|
|
307
|
+
expect(window.localStorage.getItem("agent-native.anonymous_id")).toBe(
|
|
308
|
+
null,
|
|
309
|
+
);
|
|
310
|
+
expect(document.cookie).not.toMatch(/an_aid=[^;]/);
|
|
311
|
+
} finally {
|
|
312
|
+
delete (window as Window & { __AGENT_NATIVE_CONFIG__?: unknown })
|
|
313
|
+
.__AGENT_NATIVE_CONFIG__;
|
|
314
|
+
window.sessionStorage.removeItem("an_consent_region");
|
|
315
|
+
Reflect.deleteProperty(
|
|
316
|
+
globalThis,
|
|
317
|
+
Symbol.for("agent-native.client.consent"),
|
|
318
|
+
);
|
|
319
|
+
Reflect.deleteProperty(navigator, "sendBeacon");
|
|
320
|
+
window.happyDOM.setURL(originalHref);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
250
324
|
it("sends a beta app's sign-up events to beta Analytics", async () => {
|
|
251
325
|
const originalHref = window.location.href;
|
|
252
326
|
const sendBeacon = vi.fn(() => true);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
captureAttribution,
|
|
5
|
+
getAnalyticsAnonymousId,
|
|
5
6
|
getAnalyticsSessionId,
|
|
6
7
|
} from "@agent-native/core/client/analytics";
|
|
7
8
|
import { frameworkRoutePrefix } from "@agent-native/core/client/api-path";
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
normalizeOAuthReturnPath,
|
|
13
14
|
shouldRetryAuthSessionProbe,
|
|
14
15
|
} from "@agent-native/core/client/auth/auth-page-helpers";
|
|
16
|
+
import { whenConsentDecided } from "@agent-native/core/client/consent";
|
|
15
17
|
import { openOAuthPopup } from "@agent-native/core/client/oauth-popup";
|
|
16
18
|
import { navigateForSession } from "@agent-native/core/client/use-session";
|
|
17
19
|
import { normalizeLocaleCode } from "@agent-native/core/localization/shared";
|
|
@@ -65,7 +67,6 @@ type AuthRequestResult = {
|
|
|
65
67
|
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
66
68
|
const TAB_STORAGE_KEY = "an.onboarding.tab";
|
|
67
69
|
const PENDING_SIGNUP_EMAIL_STORAGE_KEY = "an.onboarding.pendingSignupEmail";
|
|
68
|
-
const ANALYTICS_ANONYMOUS_ID_KEY = "agent-native.anonymous_id";
|
|
69
70
|
const GOOGLE_AUTH_URL_PATH = "/_agent-native/google/auth-url";
|
|
70
71
|
const BUILDER_DESKTOP_RETURN_ORIGIN = "http://127.0.0.1:8080";
|
|
71
72
|
const useIsomorphicLayoutEffect =
|
|
@@ -261,15 +262,6 @@ async function requestJson(
|
|
|
261
262
|
return { response, data, readable };
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
function generateAnonymousId(): string {
|
|
265
|
-
try {
|
|
266
|
-
if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
267
|
-
} catch {
|
|
268
|
-
// coercion-ok: the browser fallback remains usable when crypto is unavailable.
|
|
269
|
-
}
|
|
270
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
265
|
function trackAuth(
|
|
274
266
|
app: string,
|
|
275
267
|
name: string,
|
|
@@ -298,9 +290,8 @@ function trackAuth(
|
|
|
298
290
|
}
|
|
299
291
|
).__AGENT_NATIVE_CONFIG__;
|
|
300
292
|
if (!config?.agentNativeAnalyticsPublicKey) return;
|
|
301
|
-
const anonymousId = readStorage(ANALYTICS_ANONYMOUS_ID_KEY);
|
|
302
|
-
if (!anonymousId) return;
|
|
303
293
|
const sessionId = getAnalyticsSessionId();
|
|
294
|
+
const timestamp = new Date().toISOString();
|
|
304
295
|
const endpoint = resolveLaneEndpoint(
|
|
305
296
|
config.agentNativeAnalyticsEndpoint ??
|
|
306
297
|
"https://analytics.agent-native.com/track",
|
|
@@ -313,23 +304,33 @@ function trackAuth(
|
|
|
313
304
|
}> = [{ name, properties: legacyProperties }];
|
|
314
305
|
const canonical = canonicalTrackingEvent(name, legacyProperties);
|
|
315
306
|
if (canonical) events.push(canonical);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
307
|
+
const publicKey = config.agentNativeAnalyticsPublicKey;
|
|
308
|
+
// The visitor id waits for analytics consent, as in the app bundle.
|
|
309
|
+
whenConsentDecided((decided) => {
|
|
310
|
+
const anonymousId = decided ? getAnalyticsAnonymousId() : undefined;
|
|
311
|
+
for (const event of events) {
|
|
312
|
+
const body = JSON.stringify({
|
|
313
|
+
publicKey,
|
|
314
|
+
event: event.name,
|
|
315
|
+
properties: anonymousId
|
|
316
|
+
? event.properties
|
|
317
|
+
: {
|
|
318
|
+
...event.properties,
|
|
319
|
+
analytics_consent: decided ? "withheld" : "unknown",
|
|
320
|
+
},
|
|
321
|
+
anonymousId,
|
|
322
|
+
sessionId,
|
|
323
|
+
timestamp,
|
|
324
|
+
});
|
|
325
|
+
if (navigator.sendBeacon?.(endpoint, body)) continue;
|
|
326
|
+
void fetch(endpoint, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
body,
|
|
329
|
+
keepalive: true,
|
|
330
|
+
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
331
|
+
}).catch(() => undefined);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
333
334
|
} catch {
|
|
334
335
|
// coercion-ok: analytics is best effort and cannot block authentication.
|
|
335
336
|
}
|
|
@@ -1036,16 +1037,8 @@ export function AuthPage(props: AuthPageProps) {
|
|
|
1036
1037
|
}, [apiPath, redirectToSignedInApp, runtimeBasePathResolved, view]);
|
|
1037
1038
|
|
|
1038
1039
|
React.useEffect(() => {
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
anonymousId = generateAnonymousId();
|
|
1042
|
-
writeStorage(ANALYTICS_ANONYMOUS_ID_KEY, anonymousId);
|
|
1043
|
-
}
|
|
1044
|
-
try {
|
|
1045
|
-
document.cookie = `an_aid=${encodeURIComponent(anonymousId)}; path=/; max-age=2592000; SameSite=Lax`;
|
|
1046
|
-
} catch {
|
|
1047
|
-
// coercion-ok: attribution cookies are optional for authentication.
|
|
1048
|
-
}
|
|
1040
|
+
// Stores `an_aid` only with analytics consent, and asks for the region.
|
|
1041
|
+
getAnalyticsAnonymousId();
|
|
1049
1042
|
// Capture is once per page load, so it must wait for a workspace's
|
|
1050
1043
|
// inferred base path or `landing_path` falls back to the wrong home.
|
|
1051
1044
|
if (runtimeBasePathResolved) {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useOnboardingChecklist } from "@agent-native/core/client/onboarding/use-onboarding-checklist";
|
|
2
|
+
import { IconCheck, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
|
3
|
+
import type { ComponentType, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { Button } from "../../ui/button.js";
|
|
6
|
+
import { Progress } from "../../ui/progress.js";
|
|
7
|
+
import { Skeleton } from "../../ui/skeleton.js";
|
|
8
|
+
import { cn } from "../../utils.js";
|
|
9
|
+
|
|
10
|
+
export interface GettingStartedLabels {
|
|
11
|
+
title: string;
|
|
12
|
+
progress: (completeCount: number, totalCount: number) => string;
|
|
13
|
+
/** Read by screen readers on a finished row; the check mark is decorative. */
|
|
14
|
+
completed: string;
|
|
15
|
+
collapse: string;
|
|
16
|
+
expand: string;
|
|
17
|
+
loadError: string;
|
|
18
|
+
retry: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GettingStartedLinkProps {
|
|
22
|
+
to: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function NativeLink({ to, className, children }: GettingStartedLinkProps) {
|
|
28
|
+
return (
|
|
29
|
+
<a href={to} className={className}>
|
|
30
|
+
{children}
|
|
31
|
+
</a>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface GettingStartedCardProps {
|
|
36
|
+
/** Name the steps were registered under with `registerOnboardingStep({ checklist })`. */
|
|
37
|
+
checklist: string;
|
|
38
|
+
labels: GettingStartedLabels;
|
|
39
|
+
/** Localized row titles by step id; a step without one shows its registered title. */
|
|
40
|
+
stepTitles?: Record<string, string>;
|
|
41
|
+
/** Router link for in-app rows so a click keeps the SPA and Cmd/Ctrl-click opens a tab. */
|
|
42
|
+
LinkComponent?: ComponentType<GettingStartedLinkProps>;
|
|
43
|
+
/** Number of rows to reserve while the first answer loads. */
|
|
44
|
+
expectedSteps?: number;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Progress checklist pinned to the app sidebar. Each app registers its own
|
|
50
|
+
* steps on the onboarding registry; the card only renders what the server
|
|
51
|
+
* derived. It renders nothing once every step is done.
|
|
52
|
+
*/
|
|
53
|
+
export function GettingStartedCard({
|
|
54
|
+
checklist,
|
|
55
|
+
labels,
|
|
56
|
+
stepTitles,
|
|
57
|
+
LinkComponent = NativeLink,
|
|
58
|
+
expectedSteps = 6,
|
|
59
|
+
className,
|
|
60
|
+
}: GettingStartedCardProps) {
|
|
61
|
+
const {
|
|
62
|
+
steps,
|
|
63
|
+
completeCount,
|
|
64
|
+
totalCount,
|
|
65
|
+
dismissed,
|
|
66
|
+
collapsed,
|
|
67
|
+
status,
|
|
68
|
+
refresh,
|
|
69
|
+
setCollapsed,
|
|
70
|
+
} = useOnboardingChecklist(checklist);
|
|
71
|
+
|
|
72
|
+
const frame = cn(
|
|
73
|
+
"overflow-hidden rounded-[14px] border border-border bg-card text-card-foreground shadow-sm",
|
|
74
|
+
className,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (status === "loading") {
|
|
78
|
+
return (
|
|
79
|
+
<div className={frame} aria-hidden>
|
|
80
|
+
<div className="space-y-3 px-4 py-3">
|
|
81
|
+
<Skeleton className="h-5 w-32" />
|
|
82
|
+
<Skeleton className="h-1.5 w-full rounded-full" />
|
|
83
|
+
</div>
|
|
84
|
+
<div className="border-t border-border">
|
|
85
|
+
{Array.from({ length: expectedSteps }, (_, index) => (
|
|
86
|
+
<div key={index} className="flex min-h-12 items-center gap-3 px-4">
|
|
87
|
+
<Skeleton className="size-4 rounded-full" />
|
|
88
|
+
<Skeleton className="h-3.5 w-28" />
|
|
89
|
+
</div>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (status === "error") {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
role="alert"
|
|
100
|
+
className={cn(
|
|
101
|
+
frame,
|
|
102
|
+
"flex items-center justify-between gap-2 px-4 py-3",
|
|
103
|
+
)}
|
|
104
|
+
>
|
|
105
|
+
<span className="text-[13px] text-muted-foreground">
|
|
106
|
+
{labels.loadError}
|
|
107
|
+
</span>
|
|
108
|
+
<Button
|
|
109
|
+
type="button"
|
|
110
|
+
variant="outline"
|
|
111
|
+
size="xs"
|
|
112
|
+
onClick={() => void refresh()}
|
|
113
|
+
>
|
|
114
|
+
{labels.retry}
|
|
115
|
+
</Button>
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (dismissed || totalCount === 0 || completeCount === totalCount) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const progressText = labels.progress(completeCount, totalCount);
|
|
125
|
+
const ToggleIcon = collapsed ? IconChevronUp : IconChevronDown;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<section className={frame} aria-label={labels.title}>
|
|
129
|
+
<div className="space-y-3 px-4 py-3">
|
|
130
|
+
<div className="flex items-center justify-between gap-2">
|
|
131
|
+
<h2 className="text-base font-semibold leading-5">{labels.title}</h2>
|
|
132
|
+
<Button
|
|
133
|
+
type="button"
|
|
134
|
+
variant="ghost"
|
|
135
|
+
size="icon"
|
|
136
|
+
className="size-6"
|
|
137
|
+
aria-label={collapsed ? labels.expand : labels.collapse}
|
|
138
|
+
aria-expanded={!collapsed}
|
|
139
|
+
onClick={() => void setCollapsed(!collapsed)}
|
|
140
|
+
>
|
|
141
|
+
<ToggleIcon className="size-4" />
|
|
142
|
+
</Button>
|
|
143
|
+
</div>
|
|
144
|
+
<Progress
|
|
145
|
+
value={(completeCount / totalCount) * 100}
|
|
146
|
+
className="h-1.5 bg-muted"
|
|
147
|
+
aria-label={progressText}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
{collapsed ? null : (
|
|
151
|
+
<ul className="border-t border-border">
|
|
152
|
+
{steps.map((step) => {
|
|
153
|
+
const rowClass =
|
|
154
|
+
"flex min-h-12 items-center gap-3 px-4 text-[13px]";
|
|
155
|
+
const content = (
|
|
156
|
+
<>
|
|
157
|
+
<span
|
|
158
|
+
aria-hidden
|
|
159
|
+
className={cn(
|
|
160
|
+
"flex size-4 shrink-0 items-center justify-center rounded-full border",
|
|
161
|
+
step.complete
|
|
162
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
163
|
+
: "border-muted-foreground/60",
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
{step.complete ? <IconCheck className="size-3" /> : null}
|
|
167
|
+
</span>
|
|
168
|
+
<span
|
|
169
|
+
className={cn(
|
|
170
|
+
"min-w-0 flex-1 truncate",
|
|
171
|
+
step.complete && "text-muted-foreground",
|
|
172
|
+
)}
|
|
173
|
+
>
|
|
174
|
+
{stepTitles?.[step.id] ?? step.title}
|
|
175
|
+
</span>
|
|
176
|
+
{step.complete ? (
|
|
177
|
+
<span className="sr-only">{labels.completed}</span>
|
|
178
|
+
) : null}
|
|
179
|
+
</>
|
|
180
|
+
);
|
|
181
|
+
return (
|
|
182
|
+
<li key={step.id}>
|
|
183
|
+
{step.href ? (
|
|
184
|
+
<LinkComponent
|
|
185
|
+
to={step.href}
|
|
186
|
+
className={cn(
|
|
187
|
+
rowClass,
|
|
188
|
+
"hover:bg-accent/60 focus-visible:bg-accent/60 focus-visible:outline-none",
|
|
189
|
+
)}
|
|
190
|
+
>
|
|
191
|
+
{content}
|
|
192
|
+
</LinkComponent>
|
|
193
|
+
) : (
|
|
194
|
+
<div className={rowClass}>{content}</div>
|
|
195
|
+
)}
|
|
196
|
+
</li>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</ul>
|
|
200
|
+
)}
|
|
201
|
+
</section>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type AppShellSkeletonLayout,
|
|
40
40
|
} from "../shared/AppShellSkeleton.js";
|
|
41
41
|
import { ClientOnly } from "../shared/ClientOnly.js";
|
|
42
|
+
import { ConsentBanner } from "../shared/ConsentBanner.js";
|
|
42
43
|
import { EnvironmentBadge } from "../shared/EnvironmentBadge.js";
|
|
43
44
|
import { RouteTransitionIndicator } from "../shared/RouteTransitionIndicator.js";
|
|
44
45
|
import { RuntimeConfigNotice } from "../shared/RuntimeConfigNotice.js";
|
|
@@ -436,12 +437,17 @@ function ProvidersInner({
|
|
|
436
437
|
settingsShortcut: boolean;
|
|
437
438
|
children: React.ReactNode;
|
|
438
439
|
}) {
|
|
440
|
+
// The banner reads the app's locale, so it sits inside the same provider.
|
|
439
441
|
const localizedChildren =
|
|
440
442
|
i18n === false ? (
|
|
441
|
-
|
|
443
|
+
<>
|
|
444
|
+
{children}
|
|
445
|
+
{ownsDocument ? <ConsentBanner /> : null}
|
|
446
|
+
</>
|
|
442
447
|
) : (
|
|
443
448
|
<AgentNativeI18nProvider {...(i18n ?? {})}>
|
|
444
449
|
{children}
|
|
450
|
+
{ownsDocument ? <ConsentBanner /> : null}
|
|
445
451
|
</AgentNativeI18nProvider>
|
|
446
452
|
);
|
|
447
453
|
|
|
@@ -148,6 +148,7 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
148
148
|
"settings.privacyDeletionDescription":
|
|
149
149
|
"This records a deletion request; it does not delete data immediately. An administrator will verify your identity and complete the request under the deployment's retention and legal obligations.",
|
|
150
150
|
"settings.privacyDocsLink": "Read privacy and data rights",
|
|
151
|
+
"consentBanner.cookieSettings": "Cookie settings",
|
|
151
152
|
"common.cancel": "Cancel",
|
|
152
153
|
"agentChat.auth.logOut": "Log out",
|
|
153
154
|
}[key] ??
|
|
@@ -155,6 +156,11 @@ vi.mock("@agent-native/core/client/i18n", () => ({
|
|
|
155
156
|
key,
|
|
156
157
|
}));
|
|
157
158
|
|
|
159
|
+
const openConsentSettingsMock = vi.hoisted(() => vi.fn());
|
|
160
|
+
vi.mock("@agent-native/core/client/consent", () => ({
|
|
161
|
+
openConsentSettings: openConsentSettingsMock,
|
|
162
|
+
}));
|
|
163
|
+
|
|
158
164
|
vi.mock("./SchedulingTimezoneField.js", () => ({
|
|
159
165
|
SchedulingTimezoneField: () => <div data-testid="timezone" />,
|
|
160
166
|
}));
|
|
@@ -454,4 +460,29 @@ describe("AccountSettingsForm name editing", () => {
|
|
|
454
460
|
requestType: "deletion",
|
|
455
461
|
});
|
|
456
462
|
});
|
|
463
|
+
|
|
464
|
+
it("reopens the cookie banner from Privacy & data", async () => {
|
|
465
|
+
await act(async () => {
|
|
466
|
+
root.render(
|
|
467
|
+
<TooltipProvider>
|
|
468
|
+
<AccountSettingsForm />
|
|
469
|
+
</TooltipProvider>,
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
act(() => {
|
|
473
|
+
Array.from(
|
|
474
|
+
container.querySelectorAll<HTMLButtonElement>("#privacy-data button"),
|
|
475
|
+
)
|
|
476
|
+
.find((button) => button.textContent?.trim() === "Manage")
|
|
477
|
+
?.click();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
act(() => {
|
|
481
|
+
Array.from(document.querySelectorAll<HTMLButtonElement>("button"))
|
|
482
|
+
.find((button) => button.textContent?.trim() === "Cookie settings")
|
|
483
|
+
?.click();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(openConsentSettingsMock).toHaveBeenCalledTimes(1);
|
|
487
|
+
});
|
|
457
488
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openConsentSettings } from "@agent-native/core/client/consent";
|
|
1
2
|
import {
|
|
2
3
|
useActionMutation,
|
|
3
4
|
useActionQuery,
|
|
@@ -579,6 +580,13 @@ function PrivacySettings() {
|
|
|
579
580
|
{t("settings.privacyRequestError")}
|
|
580
581
|
</p>
|
|
581
582
|
)}
|
|
583
|
+
<button
|
|
584
|
+
type="button"
|
|
585
|
+
onClick={openConsentSettings}
|
|
586
|
+
className="block text-xs font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"
|
|
587
|
+
>
|
|
588
|
+
{t("consentBanner.cookieSettings")}
|
|
589
|
+
</button>
|
|
582
590
|
<a
|
|
583
591
|
href={docsUrl("privacy-and-data-rights")}
|
|
584
592
|
target="_blank"
|