@wopr-network/platform-ui-core 1.23.0 → 1.24.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-ui-core",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "Brand-agnostic AI agent platform UI — deploy as any brand via env vars",
5
5
  "repository": {
6
6
  "type": "git",
@@ -3,6 +3,18 @@ import userEvent from "@testing-library/user-event";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { CreateInstanceClient } from "../app/instances/new/create-instance-client";
5
5
 
6
+ vi.mock("next/navigation", () => ({
7
+ useRouter: vi.fn().mockReturnValue({
8
+ push: vi.fn(),
9
+ replace: vi.fn(),
10
+ prefetch: vi.fn(),
11
+ back: vi.fn(),
12
+ refresh: vi.fn(),
13
+ }),
14
+ usePathname: vi.fn().mockReturnValue("/instances/new"),
15
+ useSearchParams: vi.fn().mockReturnValue(new URLSearchParams()),
16
+ }));
17
+
6
18
  vi.mock("@/lib/marketplace-data", () => ({
7
19
  listMarketplacePlugins: vi.fn(),
8
20
  }));
@@ -2,25 +2,19 @@ import { describe, expect, it, vi } from "vitest";
2
2
 
3
3
  vi.mock("@/lib/trpc", () => ({
4
4
  trpcVanilla: {
5
- org: {
6
- orgBillingBalance: {
7
- query: vi.fn().mockResolvedValue({
8
- orgId: "org-1",
9
- balanceCents: 5000,
10
- dailyBurnCents: 100,
11
- runwayDays: 50,
12
- }),
13
- },
14
- orgMemberUsage: {
5
+ billing: {
6
+ creditsBalance: {
15
7
  query: vi.fn().mockResolvedValue({
16
- orgId: "org-1",
17
- periodStart: "2026-02-01T00:00:00.000Z",
18
- members: [],
8
+ balance_credits: 5000,
9
+ daily_burn_credits: 100,
10
+ runway_days: 50,
19
11
  }),
20
12
  },
21
- orgBillingInfo: {
13
+ billingInfo: {
22
14
  query: vi.fn().mockResolvedValue({ paymentMethods: [], invoices: [] }),
23
15
  },
16
+ },
17
+ org: {
24
18
  orgTopupCheckout: {
25
19
  mutate: vi.fn().mockResolvedValue({
26
20
  url: "https://checkout.stripe.com/test",
@@ -42,7 +36,7 @@ import {
42
36
  } from "@/lib/org-billing-api";
43
37
 
44
38
  describe("org-billing-api", () => {
45
- it("getOrgCreditBalance converts cents to dollars", async () => {
39
+ it("getOrgCreditBalance converts credits to dollars", async () => {
46
40
  const result = await getOrgCreditBalance("org-1");
47
41
  expect(result.balance).toBe(50);
48
42
  expect(result.dailyBurn).toBe(1);
@@ -9,10 +9,11 @@ interface MockMutate {
9
9
  }
10
10
 
11
11
  interface MockTrpcVanilla {
12
+ billing: {
13
+ creditsBalance: MockQuery;
14
+ billingInfo: MockQuery;
15
+ };
12
16
  org: {
13
- orgBillingBalance: MockQuery;
14
- orgMemberUsage: MockQuery;
15
- orgBillingInfo: MockQuery;
16
17
  orgTopupCheckout: MockMutate;
17
18
  orgRemovePaymentMethod: MockMutate;
18
19
  };
@@ -20,10 +21,11 @@ interface MockTrpcVanilla {
20
21
 
21
22
  vi.mock("@/lib/trpc", () => ({
22
23
  trpcVanilla: {
24
+ billing: {
25
+ creditsBalance: { query: vi.fn() },
26
+ billingInfo: { query: vi.fn() },
27
+ },
23
28
  org: {
24
- orgBillingBalance: { query: vi.fn() },
25
- orgMemberUsage: { query: vi.fn() },
26
- orgBillingInfo: { query: vi.fn() },
27
29
  orgTopupCheckout: { mutate: vi.fn() },
28
30
  orgRemovePaymentMethod: { mutate: vi.fn() },
29
31
  },
@@ -33,8 +35,8 @@ vi.mock("@/lib/trpc", () => ({
33
35
  describe("org-billing-api null guards", () => {
34
36
  it("getOrgCreditBalance handles empty response", async () => {
35
37
  const { trpcVanilla } = await import("@/lib/trpc");
36
- const { org } = trpcVanilla as unknown as MockTrpcVanilla;
37
- org.orgBillingBalance.query.mockResolvedValue({});
38
+ const { billing } = trpcVanilla as unknown as MockTrpcVanilla;
39
+ billing.creditsBalance.query.mockResolvedValue({});
38
40
 
39
41
  const { getOrgCreditBalance } = await import("@/lib/org-billing-api");
40
42
  const result = await getOrgCreditBalance("org-1");
@@ -43,11 +45,7 @@ describe("org-billing-api null guards", () => {
43
45
  expect(result.runway).toBeNull();
44
46
  });
45
47
 
46
- it("getOrgMemberUsage handles missing members array", async () => {
47
- const { trpcVanilla } = await import("@/lib/trpc");
48
- const { org } = trpcVanilla as unknown as MockTrpcVanilla;
49
- org.orgMemberUsage.query.mockResolvedValue({ orgId: "o", periodStart: "2026-01-01" });
50
-
48
+ it("getOrgMemberUsage returns stub with empty members", async () => {
51
49
  const { getOrgMemberUsage } = await import("@/lib/org-billing-api");
52
50
  const result = await getOrgMemberUsage("org-1");
53
51
  expect(result.members).toEqual([]);
@@ -55,8 +53,8 @@ describe("org-billing-api null guards", () => {
55
53
 
56
54
  it("getOrgBillingInfo handles empty response", async () => {
57
55
  const { trpcVanilla } = await import("@/lib/trpc");
58
- const { org } = trpcVanilla as unknown as MockTrpcVanilla;
59
- org.orgBillingInfo.query.mockResolvedValue({});
56
+ const { billing } = trpcVanilla as unknown as MockTrpcVanilla;
57
+ billing.billingInfo.query.mockResolvedValue({});
60
58
 
61
59
  const { getOrgBillingInfo } = await import("@/lib/org-billing-api");
62
60
  const result = await getOrgBillingInfo("org-1");
@@ -24,7 +24,7 @@ import { Button } from "@/components/ui/button";
24
24
  import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
25
25
  import { usePageContext } from "@/hooks/use-page-context";
26
26
  import { useWebMCP } from "@/hooks/use-webmcp";
27
- import { productName } from "@/lib/brand-config";
27
+ import { getBrandConfig, productName } from "@/lib/brand-config";
28
28
  import { ChatProvider } from "@/lib/chat/chat-context";
29
29
 
30
30
  export default function DashboardLayout({
@@ -107,7 +107,7 @@ export default function DashboardLayout({
107
107
  </motion.main>
108
108
  </AnimatePresence>
109
109
  </div>
110
- {!pathname.startsWith("/chat") && <ChatWidget />}
110
+ {getBrandConfig().chatEnabled && !pathname.startsWith("/chat") && <ChatWidget />}
111
111
  </ChatProvider>
112
112
  );
113
113
  }
@@ -1,25 +1,24 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  const {
4
- mockOrgBillingBalanceQuery,
5
- mockOrgMemberUsageQuery,
6
- mockOrgBillingInfoQuery,
4
+ mockCreditsBalanceQuery,
5
+ mockBillingInfoQuery,
7
6
  mockOrgTopupCheckoutMutate,
8
7
  mockOrgSetDefaultPaymentMethodMutate,
9
8
  } = vi.hoisted(() => ({
10
- mockOrgBillingBalanceQuery: vi.fn(),
11
- mockOrgMemberUsageQuery: vi.fn(),
12
- mockOrgBillingInfoQuery: vi.fn(),
9
+ mockCreditsBalanceQuery: vi.fn(),
10
+ mockBillingInfoQuery: vi.fn(),
13
11
  mockOrgTopupCheckoutMutate: vi.fn(),
14
12
  mockOrgSetDefaultPaymentMethodMutate: vi.fn(),
15
13
  }));
16
14
 
17
15
  vi.mock("@/lib/trpc", () => ({
18
16
  trpcVanilla: {
17
+ billing: {
18
+ creditsBalance: { query: mockCreditsBalanceQuery },
19
+ billingInfo: { query: mockBillingInfoQuery },
20
+ },
19
21
  org: {
20
- orgBillingBalance: { query: mockOrgBillingBalanceQuery },
21
- orgMemberUsage: { query: mockOrgMemberUsageQuery },
22
- orgBillingInfo: { query: mockOrgBillingInfoQuery },
23
22
  orgTopupCheckout: { mutate: mockOrgTopupCheckoutMutate },
24
23
  orgSetDefaultPaymentMethod: { mutate: mockOrgSetDefaultPaymentMethodMutate },
25
24
  },
@@ -38,11 +37,11 @@ import {
38
37
  describe("getOrgCreditBalance", () => {
39
38
  afterEach(() => vi.clearAllMocks());
40
39
 
41
- it("converts cents to dollars and returns balance", async () => {
42
- mockOrgBillingBalanceQuery.mockResolvedValue({
43
- balanceCents: 5000,
44
- dailyBurnCents: 200,
45
- runwayDays: 25,
40
+ it("converts credits to dollars and returns balance", async () => {
41
+ mockCreditsBalanceQuery.mockResolvedValue({
42
+ balance_credits: 5000,
43
+ daily_burn_credits: 200,
44
+ runway_days: 25,
46
45
  });
47
46
 
48
47
  const result = await getOrgCreditBalance("org-1");
@@ -51,14 +50,29 @@ describe("getOrgCreditBalance", () => {
51
50
  dailyBurn: 2,
52
51
  runway: 25,
53
52
  });
54
- expect(mockOrgBillingBalanceQuery).toHaveBeenCalledWith({ orgId: "org-1" });
53
+ expect(mockCreditsBalanceQuery).toHaveBeenCalledWith({});
54
+ });
55
+
56
+ it("falls back to legacy cents fields", async () => {
57
+ mockCreditsBalanceQuery.mockResolvedValue({
58
+ balance_cents: 3000,
59
+ daily_burn_cents: 100,
60
+ runway_days: 30,
61
+ });
62
+
63
+ const result = await getOrgCreditBalance("org-1");
64
+ expect(result).toEqual({
65
+ balance: 30,
66
+ dailyBurn: 1,
67
+ runway: 30,
68
+ });
55
69
  });
56
70
 
57
71
  it("defaults to zero balance when response fields are null", async () => {
58
- mockOrgBillingBalanceQuery.mockResolvedValue({
59
- balanceCents: null,
60
- dailyBurnCents: null,
61
- runwayDays: null,
72
+ mockCreditsBalanceQuery.mockResolvedValue({
73
+ balance_credits: null,
74
+ daily_burn_credits: null,
75
+ runway_days: null,
62
76
  });
63
77
 
64
78
  const result = await getOrgCreditBalance("org-1");
@@ -70,7 +84,7 @@ describe("getOrgCreditBalance", () => {
70
84
  });
71
85
 
72
86
  it("defaults to zero balance when response fields are undefined", async () => {
73
- mockOrgBillingBalanceQuery.mockResolvedValue({});
87
+ mockCreditsBalanceQuery.mockResolvedValue({});
74
88
 
75
89
  const result = await getOrgCreditBalance("org-1");
76
90
  expect(result).toEqual({
@@ -81,101 +95,17 @@ describe("getOrgCreditBalance", () => {
81
95
  });
82
96
 
83
97
  it("propagates tRPC errors", async () => {
84
- mockOrgBillingBalanceQuery.mockRejectedValue(new Error("Forbidden"));
98
+ mockCreditsBalanceQuery.mockRejectedValue(new Error("Forbidden"));
85
99
  await expect(getOrgCreditBalance("org-1")).rejects.toThrow("Forbidden");
86
100
  });
87
101
  });
88
102
 
89
103
  describe("getOrgMemberUsage", () => {
90
- afterEach(() => vi.clearAllMocks());
91
-
92
- it("transforms member usage with cents-to-dollars conversion", async () => {
93
- mockOrgMemberUsageQuery.mockResolvedValue({
94
- orgId: "org-1",
95
- periodStart: "2026-03-01",
96
- members: [
97
- {
98
- memberId: "m-1",
99
- name: "Alice",
100
- email: "alice@test.com",
101
- creditsConsumedCents: 1500,
102
- lastActiveAt: "2026-03-02T10:00:00Z",
103
- },
104
- {
105
- memberId: "m-2",
106
- name: "Bob",
107
- email: "bob@test.com",
108
- creditsConsumedCents: 300,
109
- lastActiveAt: null,
110
- },
111
- ],
112
- });
113
-
114
- const result = await getOrgMemberUsage("org-1");
115
- expect(result).toEqual({
116
- orgId: "org-1",
117
- periodStart: "2026-03-01",
118
- members: [
119
- {
120
- memberId: "m-1",
121
- name: "Alice",
122
- email: "alice@test.com",
123
- creditsConsumed: 15,
124
- lastActiveAt: "2026-03-02T10:00:00Z",
125
- },
126
- {
127
- memberId: "m-2",
128
- name: "Bob",
129
- email: "bob@test.com",
130
- creditsConsumed: 3,
131
- lastActiveAt: null,
132
- },
133
- ],
134
- });
135
- expect(mockOrgMemberUsageQuery).toHaveBeenCalledWith({ orgId: "org-1" });
136
- });
137
-
138
- it("defaults member fields when properties are missing", async () => {
139
- mockOrgMemberUsageQuery.mockResolvedValue({
140
- orgId: "org-1",
141
- periodStart: "2026-03-01",
142
- members: [{}],
143
- });
144
-
145
- const result = await getOrgMemberUsage("org-1");
146
- expect(result.members[0]).toEqual({
147
- memberId: "",
148
- name: "",
149
- email: "",
150
- creditsConsumed: 0,
151
- lastActiveAt: null,
152
- });
153
- });
154
-
155
- it("handles null members array", async () => {
156
- mockOrgMemberUsageQuery.mockResolvedValue({
157
- orgId: "org-1",
158
- periodStart: "2026-03-01",
159
- members: null,
160
- });
161
-
162
- const result = await getOrgMemberUsage("org-1");
163
- expect(result.members).toEqual([]);
164
- });
165
-
166
- it("falls back orgId and periodStart when missing from response", async () => {
167
- mockOrgMemberUsageQuery.mockResolvedValue({
168
- members: [],
169
- });
170
-
104
+ it("returns stub data with empty members array", async () => {
171
105
  const result = await getOrgMemberUsage("org-1");
172
106
  expect(result.orgId).toBe("org-1");
173
- expect(result.periodStart).toBe("");
174
- });
175
-
176
- it("propagates tRPC errors", async () => {
177
- mockOrgMemberUsageQuery.mockRejectedValue(new Error("Server error"));
178
- await expect(getOrgMemberUsage("org-1")).rejects.toThrow("Server error");
107
+ expect(result.members).toEqual([]);
108
+ expect(result.periodStart).toBeTruthy();
179
109
  });
180
110
  });
181
111
 
@@ -195,7 +125,7 @@ describe("getOrgBillingInfo", () => {
195
125
  hostedLineItems: undefined,
196
126
  },
197
127
  ];
198
- mockOrgBillingInfoQuery.mockResolvedValue({ paymentMethods, invoices });
128
+ mockBillingInfoQuery.mockResolvedValue({ paymentMethods, invoices });
199
129
 
200
130
  const result = await getOrgBillingInfo("org-1");
201
131
  expect(result).toEqual({
@@ -212,11 +142,11 @@ describe("getOrgBillingInfo", () => {
212
142
  },
213
143
  ],
214
144
  });
215
- expect(mockOrgBillingInfoQuery).toHaveBeenCalledWith({ orgId: "org-1" });
145
+ expect(mockBillingInfoQuery).toHaveBeenCalledWith({});
216
146
  });
217
147
 
218
148
  it("defaults to empty arrays when fields are null", async () => {
219
- mockOrgBillingInfoQuery.mockResolvedValue({
149
+ mockBillingInfoQuery.mockResolvedValue({
220
150
  paymentMethods: null,
221
151
  invoices: null,
222
152
  });
@@ -226,15 +156,16 @@ describe("getOrgBillingInfo", () => {
226
156
  });
227
157
 
228
158
  it("defaults to empty arrays when fields are undefined", async () => {
229
- mockOrgBillingInfoQuery.mockResolvedValue({});
159
+ mockBillingInfoQuery.mockResolvedValue({});
230
160
 
231
161
  const result = await getOrgBillingInfo("org-1");
232
162
  expect(result).toEqual({ paymentMethods: [], invoices: [] });
233
163
  });
234
164
 
235
- it("propagates tRPC errors", async () => {
236
- mockOrgBillingInfoQuery.mockRejectedValue(new Error("Not found"));
237
- await expect(getOrgBillingInfo("org-1")).rejects.toThrow("Not found");
165
+ it("returns defaults on tRPC error instead of throwing", async () => {
166
+ mockBillingInfoQuery.mockRejectedValue(new Error("Not found"));
167
+ const result = await getOrgBillingInfo("org-1");
168
+ expect(result).toEqual({ paymentMethods: [], invoices: [] });
238
169
  });
239
170
  });
240
171
 
@@ -90,6 +90,9 @@ export interface BrandConfig {
90
90
 
91
91
  /** Sidebar navigation items. Each has a label and href. */
92
92
  navItems: Array<{ label: string; href: string }>;
93
+
94
+ /** Whether the embedded chat widget is enabled (default true). */
95
+ chatEnabled: boolean;
93
96
  }
94
97
 
95
98
  /**
@@ -154,6 +157,7 @@ function envDefaults(): BrandConfig {
154
157
  companyLegalName: process.env.NEXT_PUBLIC_BRAND_COMPANY_LEGAL || "Platform Inc.",
155
158
  price: process.env.NEXT_PUBLIC_BRAND_PRICE || "",
156
159
  homePath: process.env.NEXT_PUBLIC_BRAND_HOME_PATH || "/marketplace",
160
+ chatEnabled: process.env.NEXT_PUBLIC_BRAND_CHAT_ENABLED !== "false",
157
161
  navItems: parseNavItems(process.env.NEXT_PUBLIC_BRAND_NAV_ITEMS) ?? [
158
162
  { label: "Dashboard", href: "/dashboard" },
159
163
  { label: "Chat", href: "/chat" },