@carlonicora/nextjs-jsonapi 1.77.2 → 1.78.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/dist/AssistantInterface-BYgI5z1-.d.mts +12 -0
- package/dist/AssistantInterface-DfDcz0gJ.d.ts +12 -0
- package/dist/AssistantMessageInterface-D0Kwf8CR.d.mts +36 -0
- package/dist/AssistantMessageInterface-DS_tyJTV.d.ts +36 -0
- package/dist/{BlockNoteEditor-ILXF7KHN.js → BlockNoteEditor-2G5UYALC.js} +14 -14
- package/dist/{BlockNoteEditor-ILXF7KHN.js.map → BlockNoteEditor-2G5UYALC.js.map} +1 -1
- package/dist/{BlockNoteEditor-ALVN35PS.mjs → BlockNoteEditor-JXK3JGKJ.mjs} +4 -4
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-FKLP4NED.js → chunk-FDJQRIMY.js} +320 -18
- package/dist/chunk-FDJQRIMY.js.map +1 -0
- package/dist/{chunk-XI35ALWY.mjs → chunk-I65SSQ5Z.mjs} +303 -1
- package/dist/chunk-I65SSQ5Z.mjs.map +1 -0
- package/dist/{chunk-ICD6MZ43.mjs → chunk-NB6TIKHK.mjs} +2090 -1463
- package/dist/chunk-NB6TIKHK.mjs.map +1 -0
- package/dist/{chunk-JOJZRGZL.mjs → chunk-NZOUEN67.mjs} +2 -2
- package/dist/{chunk-OTZEXASK.js → chunk-X4YDETTD.js} +11 -11
- package/dist/{chunk-OTZEXASK.js.map → chunk-X4YDETTD.js.map} +1 -1
- package/dist/{chunk-VSWQ7WIV.js → chunk-ZEDB6JVB.js} +1359 -732
- package/dist/chunk-ZEDB6JVB.js.map +1 -0
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +21 -2
- package/dist/components/index.d.ts +21 -2
- package/dist/components/index.js +10 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +9 -3
- package/dist/contexts/index.d.mts +26 -2
- package/dist/contexts/index.d.ts +26 -2
- package/dist/contexts/index.js +8 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +7 -3
- package/dist/core/index.d.mts +110 -3
- package/dist/core/index.d.ts +110 -3
- package/dist/core/index.js +14 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +2 -0
- package/src/contexts/index.ts +1 -0
- package/src/core/index.ts +4 -0
- package/src/core/registry/ModuleRegistry.ts +9 -0
- package/src/features/assistant/AssistantModule.ts +19 -0
- package/src/features/assistant/components/containers/AssistantContainer.tsx +56 -0
- package/src/features/assistant/components/containers/__tests__/AssistantContainer.spec.tsx +101 -0
- package/src/features/assistant/components/index.ts +1 -0
- package/src/features/assistant/components/parts/AssistantComposer.tsx +56 -0
- package/src/features/assistant/components/parts/AssistantEmptyState.tsx +47 -0
- package/src/features/assistant/components/parts/AssistantSidebar.tsx +64 -0
- package/src/features/assistant/components/parts/AssistantStatusLine.tsx +19 -0
- package/src/features/assistant/components/parts/AssistantThread.tsx +36 -0
- package/src/features/assistant/components/parts/AssistantThreadHeader.tsx +91 -0
- package/src/features/assistant/components/parts/__tests__/AssistantComposer.spec.tsx +32 -0
- package/src/features/assistant/components/parts/__tests__/AssistantEmptyState.spec.tsx +27 -0
- package/src/features/assistant/components/parts/__tests__/AssistantSidebar.spec.tsx +58 -0
- package/src/features/assistant/components/parts/__tests__/AssistantStatusLine.spec.tsx +19 -0
- package/src/features/assistant/components/parts/__tests__/AssistantThread.spec.tsx +39 -0
- package/src/features/assistant/components/parts/__tests__/AssistantThreadHeader.spec.tsx +67 -0
- package/src/features/assistant/contexts/AssistantContext.tsx +255 -0
- package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +375 -0
- package/src/features/assistant/data/Assistant.ts +37 -0
- package/src/features/assistant/data/AssistantInterface.ts +11 -0
- package/src/features/assistant/data/AssistantService.ts +79 -0
- package/src/features/assistant/data/index.ts +3 -0
- package/src/features/assistant/index.ts +2 -0
- package/src/features/assistant/utils/__tests__/groupThreadsByBucket.spec.ts +24 -0
- package/src/features/assistant/utils/__tests__/resolveReferenceableModules.spec.ts +92 -0
- package/src/features/assistant/utils/groupThreadsByBucket.ts +26 -0
- package/src/features/assistant/utils/resolveReferenceableModules.ts +14 -0
- package/src/features/assistant-message/AssistantMessageModule.ts +28 -0
- package/src/features/assistant-message/components/MessageItem.tsx +60 -0
- package/src/features/assistant-message/components/MessageList.tsx +38 -0
- package/src/features/assistant-message/components/__tests__/MessageItem.spec.tsx +108 -0
- package/src/features/assistant-message/components/index.ts +2 -0
- package/src/features/assistant-message/components/parts/ReferenceBadges.tsx +46 -0
- package/src/features/assistant-message/components/parts/SuggestedFollowUps.tsx +52 -0
- package/src/features/assistant-message/components/parts/__tests__/ReferenceBadges.spec.tsx +59 -0
- package/src/features/assistant-message/components/parts/__tests__/SuggestedFollowUps.spec.tsx +29 -0
- package/src/features/assistant-message/data/AssistantMessage.ts +95 -0
- package/src/features/assistant-message/data/AssistantMessageInterface.ts +21 -0
- package/src/features/assistant-message/data/AssistantMessageService.ts +40 -0
- package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +158 -0
- package/src/features/assistant-message/data/index.ts +3 -0
- package/src/features/assistant-message/index.ts +2 -0
- package/src/features/user/contexts/CurrentUserContext.tsx +5 -13
- package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +141 -0
- package/src/hooks/usePushNotifications.ts +3 -0
- package/src/index.ts +4 -0
- package/dist/HowToInterface-BKhnkzBp.d.ts +0 -17
- package/dist/HowToInterface-Cj8OuQFf.d.mts +0 -17
- package/dist/chunk-FKLP4NED.js.map +0 -1
- package/dist/chunk-ICD6MZ43.mjs.map +0 -1
- package/dist/chunk-VSWQ7WIV.js.map +0 -1
- package/dist/chunk-XI35ALWY.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-ALVN35PS.mjs.map → BlockNoteEditor-JXK3JGKJ.mjs.map} +0 -0
- /package/dist/{chunk-JOJZRGZL.mjs.map → chunk-NZOUEN67.mjs.map} +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { AbstractApiData } from "../../../../core/abstracts/AbstractApiData";
|
|
3
|
+
import { DataClassRegistry } from "../../../../core/registry/DataClassRegistry";
|
|
4
|
+
import { ModuleRegistry } from "../../../../core/registry/ModuleRegistry";
|
|
5
|
+
import { JsonApiHydratedDataInterface } from "../../../../core/interfaces/JsonApiHydratedDataInterface";
|
|
6
|
+
import { ApiRequestDataTypeInterface } from "../../../../core/interfaces/ApiRequestDataTypeInterface";
|
|
7
|
+
import { AssistantMessage } from "../AssistantMessage";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Minimal test-only model — avoids collision with any real app module.
|
|
11
|
+
// identifierFields = ["name"] resolves "Acme" from attributes.name.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
class TestAccount extends AbstractApiData {
|
|
14
|
+
static identifierFields: string[] = ["name"];
|
|
15
|
+
|
|
16
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
17
|
+
super.rehydrate(data);
|
|
18
|
+
return this;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
createJsonApi(_data?: any): any {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const testAccountModule: ApiRequestDataTypeInterface = {
|
|
27
|
+
name: "test-accounts",
|
|
28
|
+
model: TestAccount,
|
|
29
|
+
} as any;
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Setup / teardown
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
const assistantMessageModule: ApiRequestDataTypeInterface = {
|
|
35
|
+
name: "assistant-messages",
|
|
36
|
+
model: AssistantMessage,
|
|
37
|
+
} as any;
|
|
38
|
+
|
|
39
|
+
const assistantModule: ApiRequestDataTypeInterface = {
|
|
40
|
+
name: "assistants",
|
|
41
|
+
model: class {},
|
|
42
|
+
} as any;
|
|
43
|
+
|
|
44
|
+
beforeAll(() => {
|
|
45
|
+
DataClassRegistry.clear();
|
|
46
|
+
DataClassRegistry.registerObjectClass(testAccountModule, TestAccount);
|
|
47
|
+
ModuleRegistry.register("TestAccount", testAccountModule);
|
|
48
|
+
ModuleRegistry.register("AssistantMessage", assistantMessageModule);
|
|
49
|
+
ModuleRegistry.register("Assistant", assistantModule);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(() => {
|
|
53
|
+
DataClassRegistry.clear();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Helpers
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
function makeRehydratedMessage(jsonApiData: any, included: any[] = []): AssistantMessage {
|
|
60
|
+
const data: JsonApiHydratedDataInterface = {
|
|
61
|
+
jsonApi: jsonApiData,
|
|
62
|
+
included,
|
|
63
|
+
};
|
|
64
|
+
return new AssistantMessage().rehydrate(data);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Tests
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
describe("AssistantMessage.rehydrate", () => {
|
|
71
|
+
it("rehydrates `references` as an array of the correct Module's model", () => {
|
|
72
|
+
// GIVEN: JSON:API assistant-message with a `references` relationship pointing to
|
|
73
|
+
// a test-accounts entity, plus that entity in the `included` array.
|
|
74
|
+
const jsonApiData = {
|
|
75
|
+
id: "msg-1",
|
|
76
|
+
type: "assistant-messages",
|
|
77
|
+
attributes: {
|
|
78
|
+
role: "assistant",
|
|
79
|
+
content: "Here is the account.",
|
|
80
|
+
position: 1,
|
|
81
|
+
},
|
|
82
|
+
relationships: {
|
|
83
|
+
references: {
|
|
84
|
+
data: [{ type: "test-accounts", id: "acc-1" }],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const included = [
|
|
89
|
+
{
|
|
90
|
+
type: "test-accounts",
|
|
91
|
+
id: "acc-1",
|
|
92
|
+
attributes: { name: "Acme" },
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
// WHEN
|
|
97
|
+
const message = makeRehydratedMessage(jsonApiData, included);
|
|
98
|
+
|
|
99
|
+
// THEN: references should contain one hydrated TestAccount instance
|
|
100
|
+
expect(message.references).toHaveLength(1);
|
|
101
|
+
|
|
102
|
+
const ref = message.references[0] as any;
|
|
103
|
+
// Type and id should be populated from the included entry
|
|
104
|
+
expect(ref.type).toBe("test-accounts");
|
|
105
|
+
expect(ref.id).toBe("acc-1");
|
|
106
|
+
// identifier reads from identifierFields = ["name"] → attributes.name = "Acme"
|
|
107
|
+
expect(ref.identifier).toBe("Acme");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns an empty array when the relationship is absent", () => {
|
|
111
|
+
// GIVEN: JSON:API assistant-message with no `references` attribute or relationship
|
|
112
|
+
const jsonApiData = {
|
|
113
|
+
id: "msg-2",
|
|
114
|
+
type: "assistant-messages",
|
|
115
|
+
attributes: {
|
|
116
|
+
role: "user",
|
|
117
|
+
content: "Hello.",
|
|
118
|
+
position: 0,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// WHEN
|
|
123
|
+
const message = makeRehydratedMessage(jsonApiData, []);
|
|
124
|
+
|
|
125
|
+
// THEN
|
|
126
|
+
expect(message.references).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("AssistantMessage.buildOptimistic", () => {
|
|
131
|
+
it("creates a user message with a tmp-prefixed id and the given content + position", () => {
|
|
132
|
+
const msg = AssistantMessage.buildOptimistic({
|
|
133
|
+
content: "hello",
|
|
134
|
+
assistantId: "a-1",
|
|
135
|
+
position: 3,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
expect(msg.id.startsWith("tmp-")).toBe(true);
|
|
139
|
+
expect(msg.role).toBe("user");
|
|
140
|
+
expect(msg.content).toBe("hello");
|
|
141
|
+
expect(msg.position).toBe(3);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("allows an omitted assistantId (first-message case)", () => {
|
|
145
|
+
const msg = AssistantMessage.buildOptimistic({ content: "first", position: 1 });
|
|
146
|
+
|
|
147
|
+
expect(msg.id.startsWith("tmp-")).toBe(true);
|
|
148
|
+
expect(msg.role).toBe("user");
|
|
149
|
+
expect(msg.content).toBe("first");
|
|
150
|
+
expect(msg.position).toBe(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("produces distinct ids across successive calls", () => {
|
|
154
|
+
const a = AssistantMessage.buildOptimistic({ content: "x", position: 1 });
|
|
155
|
+
const b = AssistantMessage.buildOptimistic({ content: "x", position: 2 });
|
|
156
|
+
expect(a.id).not.toEqual(b.id);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { getCookie } from "cookies-next";
|
|
4
3
|
import { useAtom } from "jotai";
|
|
5
4
|
import { atomWithStorage } from "jotai/utils";
|
|
6
5
|
import { usePathname } from "next/navigation";
|
|
@@ -46,11 +45,6 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
|
|
|
46
45
|
|
|
47
46
|
const [dehydratedUser, setDehydratedUser] = useAtom(userAtom);
|
|
48
47
|
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const token = getCookie("token");
|
|
51
|
-
if (!token && dehydratedUser) setDehydratedUser(null);
|
|
52
|
-
}, [dehydratedUser, setDehydratedUser]);
|
|
53
|
-
|
|
54
48
|
const matchUrlToModule = (_params?: { path: string }): ModuleWithPermissions | undefined => {
|
|
55
49
|
const moduleKeys = Object.getOwnPropertyNames(Modules).filter(
|
|
56
50
|
(key) => key !== "prototype" && key !== "_factory" && key !== "length" && key !== "name",
|
|
@@ -148,13 +142,9 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
|
|
|
148
142
|
try {
|
|
149
143
|
const fullUser = await UserService.findFullUser();
|
|
150
144
|
if (fullUser) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
setUser(fullUser);
|
|
155
|
-
|
|
156
|
-
// Update authentication cookies with fresh user data
|
|
157
|
-
// Skip when triggered by WebSocket to prevent page reload (Server Actions modify cookies)
|
|
145
|
+
// Update authentication cookies with fresh user data BEFORE writing the atom,
|
|
146
|
+
// so downstream observers see cookies-then-user, never user-then-cookies.
|
|
147
|
+
// Skip when triggered by WebSocket to prevent page reload (Server Actions modify cookies).
|
|
158
148
|
if (!options?.skipCookieUpdate) {
|
|
159
149
|
await getTokenHandler()?.updateToken({
|
|
160
150
|
userId: fullUser.id,
|
|
@@ -167,6 +157,8 @@ export const CurrentUserProvider = ({ children }: { children: React.ReactNode })
|
|
|
167
157
|
})),
|
|
168
158
|
});
|
|
169
159
|
}
|
|
160
|
+
|
|
161
|
+
setDehydratedUser(fullUser.dehydrate() as any);
|
|
170
162
|
}
|
|
171
163
|
} catch (error) {
|
|
172
164
|
console.error("Failed to refresh user data:", error);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
vi.mock("cookies-next", () => ({
|
|
5
|
+
getCookie: vi.fn(() => undefined),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock("next/navigation", () => ({
|
|
9
|
+
usePathname: () => "/",
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("../../../../contexts/SocketContext", () => ({
|
|
13
|
+
useSocketContext: () => ({ socket: null, isConnected: false }),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("../../../../core", () => ({
|
|
17
|
+
Modules: { User: { name: "users" } },
|
|
18
|
+
rehydrate: (_module: unknown, data: any) => data,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock("../../../../permissions", () => ({
|
|
22
|
+
Action: { Read: "read", Write: "write", Create: "create", Update: "update", Delete: "delete" },
|
|
23
|
+
checkPermissions: () => true,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
vi.mock("../../../../roles", () => ({
|
|
27
|
+
getRoleId: () => ({ Administrator: "admin" }),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("../../../auth/config", () => ({
|
|
31
|
+
getTokenHandler: vi.fn(() => ({
|
|
32
|
+
updateToken: vi.fn().mockResolvedValue(undefined),
|
|
33
|
+
removeToken: vi.fn().mockResolvedValue(undefined),
|
|
34
|
+
})),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock("../../data", () => ({
|
|
38
|
+
UserService: {
|
|
39
|
+
findFullUser: vi.fn(),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
import { CurrentUserProvider, useCurrentUserContext } from "../CurrentUserContext";
|
|
44
|
+
import { getCookie } from "cookies-next";
|
|
45
|
+
import { getTokenHandler } from "../../../auth/config";
|
|
46
|
+
import { UserService } from "../../data";
|
|
47
|
+
|
|
48
|
+
function Consumer() {
|
|
49
|
+
const { currentUser } = useCurrentUserContext();
|
|
50
|
+
return <div data-testid="current-user-id">{currentUser ? (currentUser as { id: string }).id : "null"}</div>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("CurrentUserProvider", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
localStorage.clear();
|
|
56
|
+
vi.clearAllMocks();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("keeps user hydrated from localStorage when token cookie is absent", async () => {
|
|
60
|
+
const storedUser = {
|
|
61
|
+
id: "user-1",
|
|
62
|
+
roles: [],
|
|
63
|
+
company: null,
|
|
64
|
+
modules: [],
|
|
65
|
+
};
|
|
66
|
+
localStorage.setItem("user", JSON.stringify(storedUser));
|
|
67
|
+
vi.mocked(getCookie).mockReturnValue(undefined);
|
|
68
|
+
|
|
69
|
+
const { getByTestId } = render(
|
|
70
|
+
<CurrentUserProvider>
|
|
71
|
+
<Consumer />
|
|
72
|
+
</CurrentUserProvider>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
76
|
+
|
|
77
|
+
expect(getByTestId("current-user-id").textContent).toBe("user-1");
|
|
78
|
+
expect(localStorage.getItem("user")).toBe(JSON.stringify(storedUser));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("awaits updateToken before writing the user atom in refreshUser", async () => {
|
|
82
|
+
const oldUser = { id: "old", roles: [], company: null, modules: [] };
|
|
83
|
+
localStorage.setItem("user", JSON.stringify(oldUser));
|
|
84
|
+
|
|
85
|
+
const newDehydrated = { id: "new", roles: [], company: null, modules: [] };
|
|
86
|
+
const newFullUser = {
|
|
87
|
+
id: "new",
|
|
88
|
+
roles: [],
|
|
89
|
+
company: null,
|
|
90
|
+
modules: [],
|
|
91
|
+
dehydrate: () => newDehydrated,
|
|
92
|
+
};
|
|
93
|
+
vi.mocked(UserService.findFullUser).mockResolvedValue(newFullUser as any);
|
|
94
|
+
|
|
95
|
+
let resolveUpdateToken!: () => void;
|
|
96
|
+
const updateTokenPromise = new Promise<void>((resolve) => {
|
|
97
|
+
resolveUpdateToken = resolve;
|
|
98
|
+
});
|
|
99
|
+
const updateToken = vi.fn(async () => {
|
|
100
|
+
await updateTokenPromise;
|
|
101
|
+
});
|
|
102
|
+
vi.mocked(getTokenHandler).mockReturnValue({
|
|
103
|
+
updateToken,
|
|
104
|
+
removeToken: vi.fn().mockResolvedValue(undefined),
|
|
105
|
+
} as any);
|
|
106
|
+
|
|
107
|
+
let capturedRefreshUser: ((options?: { skipCookieUpdate?: boolean }) => Promise<void>) | undefined;
|
|
108
|
+
function InnerConsumer() {
|
|
109
|
+
const ctx = useCurrentUserContext();
|
|
110
|
+
capturedRefreshUser = ctx.refreshUser;
|
|
111
|
+
return (
|
|
112
|
+
<div data-testid="current-user-id">{ctx.currentUser ? (ctx.currentUser as { id: string }).id : "null"}</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { getByTestId } = render(
|
|
117
|
+
<CurrentUserProvider>
|
|
118
|
+
<InnerConsumer />
|
|
119
|
+
</CurrentUserProvider>,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
123
|
+
expect(getByTestId("current-user-id").textContent).toBe("old");
|
|
124
|
+
|
|
125
|
+
expect(capturedRefreshUser).toBeDefined();
|
|
126
|
+
const refreshPromise = capturedRefreshUser!();
|
|
127
|
+
|
|
128
|
+
await Promise.resolve();
|
|
129
|
+
await Promise.resolve();
|
|
130
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
131
|
+
|
|
132
|
+
expect(updateToken).toHaveBeenCalledTimes(1);
|
|
133
|
+
expect(getByTestId("current-user-id").textContent).toBe("old");
|
|
134
|
+
|
|
135
|
+
resolveUpdateToken();
|
|
136
|
+
await refreshPromise;
|
|
137
|
+
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
139
|
+
expect(getByTestId("current-user-id").textContent).toBe("new");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -44,6 +44,9 @@ export default function usePushNotifications(): void {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const vapidPublicKey = (process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || "").trim();
|
|
47
|
+
if (!vapidPublicKey) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
47
50
|
const convertedKey = urlBase64ToUint8Array(vapidPublicKey);
|
|
48
51
|
|
|
49
52
|
await navigator.serviceWorker.ready;
|
package/src/index.ts
CHANGED
|
@@ -46,3 +46,7 @@ export { showToast, showError, dismissToast, showCustomToast, type ToastOptions
|
|
|
46
46
|
// RBAC feature (data + modules only; components via /components, hooks via /client)
|
|
47
47
|
export * from "./features/rbac/data";
|
|
48
48
|
export * from "./features/rbac/rbac.module";
|
|
49
|
+
|
|
50
|
+
// Assistant + AssistantMessage feature data
|
|
51
|
+
export * from "./features/assistant/data";
|
|
52
|
+
export * from "./features/assistant-message/data";
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { C as ContentInterface, a as ContentInput } from './notification.interface-DcSuc9CL.js';
|
|
2
|
-
|
|
3
|
-
type BreadcrumbItemData = {
|
|
4
|
-
name: string;
|
|
5
|
-
href?: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
type HowToInput = ContentInput & {
|
|
9
|
-
description?: any;
|
|
10
|
-
pages?: string | undefined | null;
|
|
11
|
-
};
|
|
12
|
-
interface HowToInterface extends ContentInterface {
|
|
13
|
-
get description(): any;
|
|
14
|
-
get pages(): string | undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type { BreadcrumbItemData as B, HowToInput as H, HowToInterface as a };
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { C as ContentInterface, a as ContentInput } from './notification.interface-DG6obXUH.mjs';
|
|
2
|
-
|
|
3
|
-
type BreadcrumbItemData = {
|
|
4
|
-
name: string;
|
|
5
|
-
href?: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
type HowToInput = ContentInput & {
|
|
9
|
-
description?: any;
|
|
10
|
-
pages?: string | undefined | null;
|
|
11
|
-
};
|
|
12
|
-
interface HowToInterface extends ContentInterface {
|
|
13
|
-
get description(): any;
|
|
14
|
-
get pages(): string | undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type { BreadcrumbItemData as B, HowToInput as H, HowToInterface as a };
|