@carlonicora/nextjs-jsonapi 6.30.0 → 6.32.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.
Files changed (91) hide show
  1. package/dist/{BlockNoteEditor-SQQF7OCY.mjs → BlockNoteEditor-HQTBH527.mjs} +4 -4
  2. package/dist/{BlockNoteEditor-FTRTY4YJ.js → BlockNoteEditor-YONN45A3.js} +24 -24
  3. package/dist/{BlockNoteEditor-FTRTY4YJ.js.map → BlockNoteEditor-YONN45A3.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-76CKL6W5.mjs → BlockNoteViewer-BNZEXGVT.mjs} +2 -2
  5. package/dist/{BlockNoteViewer-B25WKYQP.js → BlockNoteViewer-YONLBNXW.js} +3 -3
  6. package/dist/{BlockNoteViewer-B25WKYQP.js.map → BlockNoteViewer-YONLBNXW.js.map} +1 -1
  7. package/dist/billing/index.js +465 -465
  8. package/dist/billing/index.mjs +3 -3
  9. package/dist/{chunk-77JC2UCF.js → chunk-5XP5G45B.js} +23 -10
  10. package/dist/chunk-5XP5G45B.js.map +1 -0
  11. package/dist/{chunk-MNN6MTEN.mjs → chunk-6DNGLY5Y.mjs} +156 -127
  12. package/dist/chunk-6DNGLY5Y.mjs.map +1 -0
  13. package/dist/{chunk-7MGXQIEP.mjs → chunk-FDKAVSZ6.mjs} +2 -2
  14. package/dist/{chunk-FJUKLVVX.mjs → chunk-HNK2O6YE.mjs} +23 -10
  15. package/dist/chunk-HNK2O6YE.mjs.map +1 -0
  16. package/dist/{chunk-S4FSGMKX.js → chunk-J37GJKCB.js} +798 -769
  17. package/dist/chunk-J37GJKCB.js.map +1 -0
  18. package/dist/{chunk-M7YK5CCE.mjs → chunk-JL6XL5NK.mjs} +9 -2
  19. package/dist/chunk-JL6XL5NK.mjs.map +1 -0
  20. package/dist/{chunk-F3EVYBMA.js → chunk-NQL2NDKH.js} +4 -4
  21. package/dist/{chunk-F3EVYBMA.js.map → chunk-NQL2NDKH.js.map} +1 -1
  22. package/dist/{chunk-ONU4WCDG.js → chunk-Q5SMFSE4.js} +11 -4
  23. package/dist/chunk-Q5SMFSE4.js.map +1 -0
  24. package/dist/client/index.js +4 -4
  25. package/dist/client/index.mjs +3 -3
  26. package/dist/components/index.js +4 -4
  27. package/dist/components/index.mjs +3 -3
  28. package/dist/contexts/index.js +4 -4
  29. package/dist/contexts/index.mjs +3 -3
  30. package/dist/core/index.js +2 -2
  31. package/dist/core/index.mjs +1 -1
  32. package/dist/email-automations/index.js +105 -105
  33. package/dist/email-automations/index.mjs +3 -3
  34. package/dist/features/analytics/index.js +114 -114
  35. package/dist/features/analytics/index.mjs +3 -3
  36. package/dist/features/analytics/server.js +4 -4
  37. package/dist/features/analytics/server.mjs +2 -2
  38. package/dist/features/auth/components/GdprConsentSection.d.ts +2 -1
  39. package/dist/features/auth/components/GdprConsentSection.d.ts.map +1 -1
  40. package/dist/features/auth/components/forms/Login.d.ts.map +1 -1
  41. package/dist/features/auth/components/forms/Logout.d.ts +7 -1
  42. package/dist/features/auth/components/forms/Logout.d.ts.map +1 -1
  43. package/dist/features/auth/components/forms/Register.d.ts.map +1 -1
  44. package/dist/features/auth/data/auth.service.d.ts +8 -3
  45. package/dist/features/auth/data/auth.service.d.ts.map +1 -1
  46. package/dist/features/auth/data/pending-registration.d.ts +2 -0
  47. package/dist/features/auth/data/pending-registration.d.ts.map +1 -1
  48. package/dist/features/auth/data/pending-registration.interface.d.ts +1 -0
  49. package/dist/features/auth/data/pending-registration.interface.d.ts.map +1 -1
  50. package/dist/features/auth/hooks/useCompleteLogin.d.ts +9 -0
  51. package/dist/features/auth/hooks/useCompleteLogin.d.ts.map +1 -0
  52. package/dist/features/blog/index.js +129 -129
  53. package/dist/features/blog/index.mjs +3 -3
  54. package/dist/features/blog/server.js +7 -7
  55. package/dist/features/blog/server.mjs +1 -1
  56. package/dist/features/help/index.js +37 -37
  57. package/dist/features/help/index.mjs +3 -3
  58. package/dist/features/tokenusage/index.js +114 -114
  59. package/dist/features/tokenusage/index.mjs +3 -3
  60. package/dist/index.js +3 -3
  61. package/dist/index.mjs +2 -2
  62. package/dist/login/config.d.ts +3 -0
  63. package/dist/login/config.d.ts.map +1 -1
  64. package/dist/login/index.d.ts +1 -1
  65. package/dist/login/index.d.ts.map +1 -1
  66. package/dist/server/index.js +3 -3
  67. package/dist/server/index.mjs +2 -2
  68. package/package.json +1 -1
  69. package/src/features/auth/components/GdprConsentSection.tsx +3 -1
  70. package/src/features/auth/components/forms/Login.tsx +4 -32
  71. package/src/features/auth/components/forms/Logout.tsx +8 -2
  72. package/src/features/auth/components/forms/Register.tsx +66 -36
  73. package/src/features/auth/data/__tests__/auth.oauth-completion.spec.ts +44 -0
  74. package/src/features/auth/data/__tests__/pending-registration.spec.ts +8 -0
  75. package/src/features/auth/data/auth.service.ts +21 -15
  76. package/src/features/auth/data/pending-registration.interface.ts +1 -0
  77. package/src/features/auth/data/pending-registration.ts +6 -0
  78. package/src/features/auth/hooks/__tests__/useCompleteLogin.spec.tsx +85 -0
  79. package/src/features/auth/hooks/useCompleteLogin.ts +47 -0
  80. package/src/login/__tests__/config.spec.ts +21 -0
  81. package/src/login/config.ts +8 -0
  82. package/src/login/index.ts +1 -0
  83. package/dist/chunk-77JC2UCF.js.map +0 -1
  84. package/dist/chunk-FJUKLVVX.mjs.map +0 -1
  85. package/dist/chunk-M7YK5CCE.mjs.map +0 -1
  86. package/dist/chunk-MNN6MTEN.mjs.map +0 -1
  87. package/dist/chunk-ONU4WCDG.js.map +0 -1
  88. package/dist/chunk-S4FSGMKX.js.map +0 -1
  89. /package/dist/{BlockNoteEditor-SQQF7OCY.mjs.map → BlockNoteEditor-HQTBH527.mjs.map} +0 -0
  90. /package/dist/{BlockNoteViewer-76CKL6W5.mjs.map → BlockNoteViewer-BNZEXGVT.mjs.map} +0 -0
  91. /package/dist/{chunk-7MGXQIEP.mjs.map → chunk-FDKAVSZ6.mjs.map} +0 -0
@@ -195,24 +195,30 @@ export class AuthService extends AbstractService {
195
195
  });
196
196
  }
197
197
 
198
+ /**
199
+ * Completes an OAuth sign-up waiting for consent. A sign-up with a usable
200
+ * waitlist invite sends no consent: the server copies it from the waitlist.
201
+ * Throws an Error with message `"<status>:<message>"` on failure.
202
+ */
198
203
  static async completeOAuthRegistration(params: {
199
204
  pendingId: string;
200
- termsAcceptedAt: string;
201
- marketingConsent: boolean;
202
- marketingConsentAt: string | null;
205
+ termsAcceptedAt?: string;
206
+ marketingConsent?: boolean;
207
+ marketingConsentAt?: string | null;
203
208
  }): Promise<{ code: string }> {
204
- const endpoint = new EndpointCreator({ endpoint: Modules.Auth, id: "oauth", childEndpoint: "complete" });
205
-
206
- const response: ApiResponseInterface = await JsonApiPost({
207
- classKey: Modules.Auth,
208
- endpoint: endpoint.generate(),
209
- body: params,
210
- overridesJsonApiCreation: true,
211
- language: "en",
209
+ const input: AuthInput = {
210
+ id: params.pendingId,
211
+ termsAcceptedAt: params.termsAcceptedAt,
212
+ marketingConsent: params.marketingConsent,
213
+ marketingConsentAt: params.marketingConsentAt,
214
+ };
215
+
216
+ return this.callApi<{ code: string }>({
217
+ type: Modules.Auth,
218
+ method: HttpMethod.POST,
219
+ endpoint: new EndpointCreator({ endpoint: Modules.Auth, id: "oauth", childEndpoint: "complete" }).generate(),
220
+ input: input,
221
+ suppressGlobalError: true,
212
222
  });
213
-
214
- if (!response.ok) throw new Error(response.error);
215
-
216
- return response.data as unknown as { code: string };
217
223
  }
218
224
  }
@@ -7,4 +7,5 @@ export interface PendingRegistrationInterface extends ApiDataInterface {
7
7
  get provider(): string;
8
8
  get hasPass(): boolean;
9
9
  get emailInUse(): boolean;
10
+ get hasInvite(): boolean;
10
11
  }
@@ -8,6 +8,7 @@ export class PendingRegistration extends AbstractApiData implements PendingRegis
8
8
  private _provider?: string;
9
9
  private _hasPass?: boolean;
10
10
  private _emailInUse?: boolean;
11
+ private _hasInvite?: boolean;
11
12
 
12
13
  get name(): string {
13
14
  return this._name ?? "";
@@ -33,6 +34,10 @@ export class PendingRegistration extends AbstractApiData implements PendingRegis
33
34
  return this._emailInUse ?? false;
34
35
  }
35
36
 
37
+ get hasInvite(): boolean {
38
+ return this._hasInvite ?? false;
39
+ }
40
+
36
41
  rehydrate(data: JsonApiHydratedDataInterface): this {
37
42
  super.rehydrate(data);
38
43
  this._name = data.jsonApi.attributes.name;
@@ -41,6 +46,7 @@ export class PendingRegistration extends AbstractApiData implements PendingRegis
41
46
  this._provider = data.jsonApi.attributes.provider;
42
47
  this._hasPass = data.jsonApi.attributes.hasPass ?? false;
43
48
  this._emailInUse = data.jsonApi.attributes.emailInUse ?? false;
49
+ this._hasInvite = data.jsonApi.attributes.hasInvite ?? false;
44
50
  return this;
45
51
  }
46
52
 
@@ -0,0 +1,85 @@
1
+ import { renderHook } from "@testing-library/react";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { AuthComponent } from "../../enums";
4
+ import { useCompleteLogin } from "../useCompleteLogin";
5
+
6
+ const mocks = vi.hoisted(() => ({
7
+ setUser: vi.fn(),
8
+ setComponentType: vi.fn(),
9
+ setPendingTwoFactor: vi.fn(),
10
+ i18nRouter: { replace: vi.fn(), push: vi.fn() },
11
+ nativeRouter: { replace: vi.fn(), push: vi.fn() },
12
+ search: { value: "" },
13
+ }));
14
+
15
+ vi.mock(
16
+ "../../../user/contexts",
17
+ () => new Proxy({ useCurrentUserContext: () => ({ setUser: mocks.setUser }) }, { has: () => true }),
18
+ );
19
+
20
+ vi.mock(
21
+ "../../contexts",
22
+ () =>
23
+ new Proxy(
24
+ {
25
+ useAuthContext: () => ({
26
+ setComponentType: mocks.setComponentType,
27
+ setPendingTwoFactor: mocks.setPendingTwoFactor,
28
+ }),
29
+ },
30
+ { has: () => true },
31
+ ),
32
+ );
33
+
34
+ vi.mock(
35
+ "../../../../hooks",
36
+ () =>
37
+ new Proxy(
38
+ {
39
+ usePageUrlGenerator:
40
+ () =>
41
+ ({ page }: { page: string }) =>
42
+ page,
43
+ useI18nRouter: () => mocks.i18nRouter,
44
+ },
45
+ { has: () => true },
46
+ ),
47
+ );
48
+
49
+ vi.mock("next/navigation", () => ({
50
+ useRouter: () => mocks.nativeRouter,
51
+ useSearchParams: () => new URLSearchParams(mocks.search.value),
52
+ }));
53
+
54
+ describe("useCompleteLogin", () => {
55
+ beforeEach(() => {
56
+ vi.clearAllMocks();
57
+ mocks.search.value = "";
58
+ });
59
+
60
+ it("a user response sets the user and goes home", () => {
61
+ const { result } = renderHook(() => useCompleteLogin());
62
+ const user = { id: "u1" } as any;
63
+ result.current(user);
64
+ expect(mocks.setUser).toHaveBeenCalledWith(user);
65
+ expect(mocks.i18nRouter.replace).toHaveBeenCalledWith("/");
66
+ expect(mocks.nativeRouter.replace).not.toHaveBeenCalled();
67
+ });
68
+
69
+ it("a callbackUrl wins over home", () => {
70
+ mocks.search.value = "callbackUrl=/it/campaigns";
71
+ const { result } = renderHook(() => useCompleteLogin());
72
+ result.current({ id: "u1" } as any);
73
+ expect(mocks.nativeRouter.replace).toHaveBeenCalledWith("/it/campaigns");
74
+ expect(mocks.i18nRouter.replace).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it("a two-factor challenge opens the challenge", () => {
78
+ const { result } = renderHook(() => useCompleteLogin());
79
+ const challenge = { pendingToken: "p" } as any;
80
+ result.current(challenge);
81
+ expect(mocks.setPendingTwoFactor).toHaveBeenCalledWith(challenge);
82
+ expect(mocks.setComponentType).toHaveBeenCalledWith(AuthComponent.TwoFactorChallenge);
83
+ expect(mocks.setUser).not.toHaveBeenCalled();
84
+ });
85
+ });
@@ -0,0 +1,47 @@
1
+ "use client";
2
+
3
+ import { useRouter, useSearchParams } from "next/navigation";
4
+ import { useCallback } from "react";
5
+ import { useI18nRouter, usePageUrlGenerator } from "../../../hooks";
6
+ import { UserInterface } from "../../user";
7
+ import { useCurrentUserContext } from "../../user/contexts";
8
+ import { useAuthContext } from "../contexts";
9
+ import { TwoFactorChallengeInterface } from "../data/two-factor-challenge.interface";
10
+ import { AuthComponent } from "../enums";
11
+
12
+ /**
13
+ * Post-login handling shared by Login and the invite sign-up in Register:
14
+ * opens the two-factor challenge when one is pending, otherwise stores the
15
+ * user and redirects to the callback URL or home.
16
+ */
17
+ export function useCompleteLogin(): (response: UserInterface | TwoFactorChallengeInterface) => void {
18
+ const { setUser } = useCurrentUserContext<UserInterface>();
19
+ const { setComponentType, setPendingTwoFactor } = useAuthContext();
20
+ const generateUrl = usePageUrlGenerator();
21
+ const i18nRouter = useI18nRouter();
22
+ const nativeRouter = useRouter(); // For URLs that already include locale
23
+ const searchParams = useSearchParams();
24
+ const callbackUrl = searchParams.get("callbackUrl");
25
+
26
+ return useCallback(
27
+ (response: UserInterface | TwoFactorChallengeInterface) => {
28
+ // Check if 2FA is required (response is TwoFactorChallengeInterface)
29
+ if ("pendingToken" in response) {
30
+ setPendingTwoFactor(response as TwoFactorChallengeInterface);
31
+ setComponentType(AuthComponent.TwoFactorChallenge);
32
+ return;
33
+ }
34
+
35
+ setUser(response as UserInterface);
36
+
37
+ // Use native router for callbackUrl since it already includes the locale prefix
38
+ // Use i18n router for normal navigation which adds locale automatically
39
+ if (callbackUrl) {
40
+ nativeRouter.replace(callbackUrl);
41
+ } else {
42
+ i18nRouter.replace(generateUrl({ page: `/` }));
43
+ }
44
+ },
45
+ [setUser, setComponentType, setPendingTwoFactor, generateUrl, i18nRouter, nativeRouter, callbackUrl],
46
+ );
47
+ }
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { configureLogin, isCompanyNameRequired } from "../index";
3
+
4
+ describe("login config — requireCompanyName", () => {
5
+ it("isCompanyNameRequired defaults to true", () => {
6
+ configureLogin({});
7
+ expect(isCompanyNameRequired()).toBe(true);
8
+ });
9
+
10
+ it("requireCompanyName false hides the company field", () => {
11
+ configureLogin({ requireCompanyName: false });
12
+ expect(isCompanyNameRequired()).toBe(false);
13
+ });
14
+
15
+ it("a later configureLogin without the option restores the default", () => {
16
+ configureLogin({ requireCompanyName: false });
17
+ expect(isCompanyNameRequired()).toBe(false);
18
+ configureLogin({});
19
+ expect(isCompanyNameRequired()).toBe(true);
20
+ });
21
+ });
@@ -3,6 +3,7 @@ let _useGoogleAuth: boolean = false;
3
3
  let _useInternalAuth: boolean = true;
4
4
  let _allowRegistration: boolean = true;
5
5
  let _registrationMode: "open" | "closed" | "waitlist" = "open";
6
+ let _requireCompanyName: boolean = true;
6
7
 
7
8
  export type RegistrationMode = "open" | "closed" | "waitlist";
8
9
 
@@ -12,6 +13,8 @@ export interface LoginConfig {
12
13
  useInternalAuth?: boolean;
13
14
  allowRegistration?: boolean;
14
15
  registrationMode?: RegistrationMode;
16
+ /** Show and require the company name on sign-up. Default `true`. */
17
+ requireCompanyName?: boolean;
15
18
  }
16
19
 
17
20
  export function configureLogin(params: LoginConfig): void {
@@ -20,6 +23,7 @@ export function configureLogin(params: LoginConfig): void {
20
23
  _useInternalAuth = params.useInternalAuth ?? true;
21
24
  _allowRegistration = params.allowRegistration ?? true;
22
25
  _registrationMode = params.registrationMode ?? "open";
26
+ _requireCompanyName = params.requireCompanyName ?? true;
23
27
  }
24
28
 
25
29
  export function isDiscordAuthEnabled(): boolean {
@@ -41,3 +45,7 @@ export function isRegistrationAllowed(): boolean {
41
45
  export function getRegistrationMode(): RegistrationMode {
42
46
  return _registrationMode;
43
47
  }
48
+
49
+ export function isCompanyNameRequired(): boolean {
50
+ return _requireCompanyName;
51
+ }
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  configureLogin,
3
+ isCompanyNameRequired,
3
4
  isDiscordAuthEnabled,
4
5
  isGoogleAuthEnabled,
5
6
  isInternalAuthEnabled,