@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,141 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
const consentStateKey = Symbol.for("agent-native.client.consent");
|
|
8
|
+
|
|
9
|
+
let root: Root | null = null;
|
|
10
|
+
let container: HTMLElement | null = null;
|
|
11
|
+
|
|
12
|
+
async function renderBanner(region: "consent" | "none") {
|
|
13
|
+
vi.stubGlobal(
|
|
14
|
+
"fetch",
|
|
15
|
+
vi.fn(async () => new Response(JSON.stringify({ region }))),
|
|
16
|
+
);
|
|
17
|
+
vi.resetModules();
|
|
18
|
+
const consent = await import("@agent-native/core/client/consent");
|
|
19
|
+
const { ConsentBanner } = await import("./ConsentBanner.js");
|
|
20
|
+
container = document.createElement("div");
|
|
21
|
+
document.body.appendChild(container);
|
|
22
|
+
root = createRoot(container);
|
|
23
|
+
await act(async () => {
|
|
24
|
+
root!.render(<ConsentBanner />);
|
|
25
|
+
});
|
|
26
|
+
await act(async () => {
|
|
27
|
+
await consent.resolveConsentRegion();
|
|
28
|
+
});
|
|
29
|
+
return consent;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function banner() {
|
|
33
|
+
return document.querySelector("[data-agent-native-consent-banner]");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function button(name: string) {
|
|
37
|
+
const match = [...document.querySelectorAll("button")].find(
|
|
38
|
+
(el) => el.textContent === name,
|
|
39
|
+
);
|
|
40
|
+
if (!match) throw new Error(`no ${name} button`);
|
|
41
|
+
return match;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function click(name: string) {
|
|
45
|
+
await act(async () => {
|
|
46
|
+
button(name).click();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("ConsentBanner", () => {
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
act(() => root?.unmount());
|
|
53
|
+
container?.remove();
|
|
54
|
+
root = null;
|
|
55
|
+
delete (globalThis as any)[consentStateKey];
|
|
56
|
+
window.sessionStorage.clear();
|
|
57
|
+
for (const cookie of document.cookie.split(";")) {
|
|
58
|
+
const name = cookie.split("=")[0]?.trim();
|
|
59
|
+
if (name) document.cookie = `${name}=; path=/; max-age=0`;
|
|
60
|
+
}
|
|
61
|
+
vi.unstubAllGlobals();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("shows nothing outside the consent region", async () => {
|
|
65
|
+
await renderBanner("none");
|
|
66
|
+
expect(banner()).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("shows on a later page once the region is already known this session", async () => {
|
|
70
|
+
window.sessionStorage.setItem("an_consent_region", "consent");
|
|
71
|
+
const fetchMock = vi.fn(async () => new Response("{}"));
|
|
72
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
73
|
+
vi.resetModules();
|
|
74
|
+
const { ConsentBanner } = await import("./ConsentBanner.js");
|
|
75
|
+
container = document.createElement("div");
|
|
76
|
+
document.body.appendChild(container);
|
|
77
|
+
root = createRoot(container);
|
|
78
|
+
await act(async () => {
|
|
79
|
+
root!.render(<ConsentBanner />);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(banner()).not.toBeNull();
|
|
83
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("stores accept-all and closes", async () => {
|
|
87
|
+
const consent = await renderBanner("consent");
|
|
88
|
+
expect(banner()).not.toBeNull();
|
|
89
|
+
|
|
90
|
+
await click("Accept all");
|
|
91
|
+
|
|
92
|
+
expect(consent.getConsentChoice()).toEqual({
|
|
93
|
+
performance: true,
|
|
94
|
+
targeting: true,
|
|
95
|
+
});
|
|
96
|
+
expect(banner()).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("stores reject-all and closes", async () => {
|
|
100
|
+
const consent = await renderBanner("consent");
|
|
101
|
+
|
|
102
|
+
await click("Reject all");
|
|
103
|
+
|
|
104
|
+
expect(consent.getConsentChoice()).toEqual({
|
|
105
|
+
performance: false,
|
|
106
|
+
targeting: false,
|
|
107
|
+
});
|
|
108
|
+
expect(banner()).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("saves one category from the settings", async () => {
|
|
112
|
+
const consent = await renderBanner("consent");
|
|
113
|
+
|
|
114
|
+
await click("Manage settings");
|
|
115
|
+
await act(async () => {
|
|
116
|
+
(
|
|
117
|
+
document.getElementById("an-consent-performance") as HTMLElement
|
|
118
|
+
).click();
|
|
119
|
+
});
|
|
120
|
+
await click("Save choices");
|
|
121
|
+
|
|
122
|
+
expect(consent.getConsentChoice()).toEqual({
|
|
123
|
+
performance: true,
|
|
124
|
+
targeting: false,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("reopens from the settings link with the stored choice", async () => {
|
|
129
|
+
const consent = await renderBanner("consent");
|
|
130
|
+
await click("Accept all");
|
|
131
|
+
|
|
132
|
+
await act(async () => consent.openConsentSettings());
|
|
133
|
+
await click("Manage settings");
|
|
134
|
+
|
|
135
|
+
expect(
|
|
136
|
+
document
|
|
137
|
+
.getElementById("an-consent-targeting")
|
|
138
|
+
?.getAttribute("aria-checked"),
|
|
139
|
+
).toBe("true");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getConsentChoice,
|
|
3
|
+
resolveConsentRegion,
|
|
4
|
+
setConsentChoice,
|
|
5
|
+
shouldShowConsentBanner,
|
|
6
|
+
subscribeConsent,
|
|
7
|
+
} from "@agent-native/core/client/consent";
|
|
8
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
9
|
+
import { Button } from "@agent-native/toolkit/ui/button";
|
|
10
|
+
import { Label } from "@agent-native/toolkit/ui/label";
|
|
11
|
+
import { Switch } from "@agent-native/toolkit/ui/switch";
|
|
12
|
+
import { useEffect, useState, useSyncExternalStore } from "react";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_PRIVACY_POLICY_HREF = "https://www.agent-native.com/privacy";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The cookie banner for visitors in the consent region. Renders nothing
|
|
18
|
+
* elsewhere, and nothing once the visitor has chosen, until
|
|
19
|
+
* `openConsentSettings()` reopens it.
|
|
20
|
+
*/
|
|
21
|
+
export function ConsentBanner({
|
|
22
|
+
privacyPolicyHref = DEFAULT_PRIVACY_POLICY_HREF,
|
|
23
|
+
}: {
|
|
24
|
+
privacyPolicyHref?: string;
|
|
25
|
+
}) {
|
|
26
|
+
const t = useT();
|
|
27
|
+
const open = useSyncExternalStore(
|
|
28
|
+
subscribeConsent,
|
|
29
|
+
shouldShowConsentBanner,
|
|
30
|
+
() => false,
|
|
31
|
+
);
|
|
32
|
+
const [managing, setManaging] = useState(false);
|
|
33
|
+
const [performance, setPerformance] = useState(false);
|
|
34
|
+
const [targeting, setTargeting] = useState(false);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
void resolveConsentRegion();
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!open) return;
|
|
42
|
+
const stored = getConsentChoice();
|
|
43
|
+
setPerformance(stored?.performance ?? false);
|
|
44
|
+
setTargeting(stored?.targeting ?? false);
|
|
45
|
+
setManaging(false);
|
|
46
|
+
}, [open]);
|
|
47
|
+
|
|
48
|
+
if (!open) return null;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<section
|
|
52
|
+
role="region"
|
|
53
|
+
aria-label={t("consentBanner.label")}
|
|
54
|
+
data-agent-native-consent-banner
|
|
55
|
+
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"
|
|
56
|
+
>
|
|
57
|
+
<p className="m-0 leading-relaxed">
|
|
58
|
+
{t("consentBanner.message")}{" "}
|
|
59
|
+
<a
|
|
60
|
+
href={privacyPolicyHref}
|
|
61
|
+
target="_blank"
|
|
62
|
+
rel="noreferrer"
|
|
63
|
+
className="underline underline-offset-2"
|
|
64
|
+
>
|
|
65
|
+
{t("consentBanner.privacyPolicy")}
|
|
66
|
+
</a>
|
|
67
|
+
</p>
|
|
68
|
+
{managing ? (
|
|
69
|
+
<div className="flex flex-col gap-2">
|
|
70
|
+
<div className="flex items-center justify-between gap-3">
|
|
71
|
+
<Label htmlFor="an-consent-necessary">
|
|
72
|
+
{t("consentBanner.necessary")}
|
|
73
|
+
</Label>
|
|
74
|
+
<Switch id="an-consent-necessary" checked disabled />
|
|
75
|
+
</div>
|
|
76
|
+
<div className="flex items-center justify-between gap-3">
|
|
77
|
+
<Label htmlFor="an-consent-performance">
|
|
78
|
+
{t("consentBanner.performance")}
|
|
79
|
+
</Label>
|
|
80
|
+
<Switch
|
|
81
|
+
id="an-consent-performance"
|
|
82
|
+
checked={performance}
|
|
83
|
+
onCheckedChange={setPerformance}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="flex items-center justify-between gap-3">
|
|
87
|
+
<Label htmlFor="an-consent-targeting">
|
|
88
|
+
{t("consentBanner.targeting")}
|
|
89
|
+
</Label>
|
|
90
|
+
<Switch
|
|
91
|
+
id="an-consent-targeting"
|
|
92
|
+
checked={targeting}
|
|
93
|
+
onCheckedChange={setTargeting}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
) : null}
|
|
98
|
+
<div className="flex flex-wrap gap-2">
|
|
99
|
+
<Button
|
|
100
|
+
variant="outline"
|
|
101
|
+
size="sm"
|
|
102
|
+
className="flex-1"
|
|
103
|
+
onClick={() =>
|
|
104
|
+
setConsentChoice({ performance: false, targeting: false })
|
|
105
|
+
}
|
|
106
|
+
>
|
|
107
|
+
{t("consentBanner.rejectAll")}
|
|
108
|
+
</Button>
|
|
109
|
+
<Button
|
|
110
|
+
variant="outline"
|
|
111
|
+
size="sm"
|
|
112
|
+
className="flex-1"
|
|
113
|
+
onClick={() =>
|
|
114
|
+
setConsentChoice({ performance: true, targeting: true })
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
{t("consentBanner.acceptAll")}
|
|
118
|
+
</Button>
|
|
119
|
+
{managing ? (
|
|
120
|
+
<Button
|
|
121
|
+
variant="outline"
|
|
122
|
+
size="sm"
|
|
123
|
+
className="flex-1"
|
|
124
|
+
onClick={() => setConsentChoice({ performance, targeting })}
|
|
125
|
+
>
|
|
126
|
+
{t("consentBanner.save")}
|
|
127
|
+
</Button>
|
|
128
|
+
) : (
|
|
129
|
+
<Button
|
|
130
|
+
variant="ghost"
|
|
131
|
+
size="sm"
|
|
132
|
+
className="flex-1"
|
|
133
|
+
onClick={() => setManaging(true)}
|
|
134
|
+
>
|
|
135
|
+
{t("consentBanner.manage")}
|
|
136
|
+
</Button>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
</section>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { toast } from "sonner";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { sonnerToast } from "./sonner.js";
|
|
5
|
+
|
|
6
|
+
describe("shared Sonner API", () => {
|
|
7
|
+
it("re-exports the toast helper", () => {
|
|
8
|
+
expect(sonnerToast).toBe(toast);
|
|
9
|
+
});
|
|
10
|
+
});
|
package/src/ui/sonner.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useTheme } from "next-themes";
|
|
2
|
-
import { Toaster as Sonner } from "sonner";
|
|
2
|
+
import { toast as sonnerToast, Toaster as Sonner } from "sonner";
|
|
3
3
|
|
|
4
4
|
import { cn } from "../utils.js";
|
|
5
5
|
|
|
@@ -55,4 +55,4 @@ const Toaster = ({ className, toastOptions, ...props }: ToasterProps) => {
|
|
|
55
55
|
);
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
export { Toaster };
|
|
58
|
+
export { Toaster, sonnerToast };
|