@voyantjs/auth-react 0.35.0 → 0.37.1

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 (56) hide show
  1. package/README.md +157 -1
  2. package/dist/hooks/index.d.ts +6 -0
  3. package/dist/hooks/index.d.ts.map +1 -1
  4. package/dist/hooks/index.js +6 -0
  5. package/dist/hooks/use-accept-invitation.d.ts +11 -0
  6. package/dist/hooks/use-accept-invitation.d.ts.map +1 -0
  7. package/dist/hooks/use-accept-invitation.js +35 -0
  8. package/dist/hooks/use-accept-invitation.test.d.ts +2 -0
  9. package/dist/hooks/use-accept-invitation.test.d.ts.map +1 -0
  10. package/dist/hooks/use-accept-invitation.test.js +36 -0
  11. package/dist/hooks/use-account-mutation.d.ts +41 -0
  12. package/dist/hooks/use-account-mutation.d.ts.map +1 -0
  13. package/dist/hooks/use-account-mutation.js +68 -0
  14. package/dist/hooks/use-account-mutation.test.d.ts +2 -0
  15. package/dist/hooks/use-account-mutation.test.d.ts.map +1 -0
  16. package/dist/hooks/use-account-mutation.test.js +67 -0
  17. package/dist/hooks/use-current-user.d.ts +2 -0
  18. package/dist/hooks/use-current-user.d.ts.map +1 -1
  19. package/dist/hooks/use-password-reset.d.ts +20 -0
  20. package/dist/hooks/use-password-reset.d.ts.map +1 -0
  21. package/dist/hooks/use-password-reset.js +83 -0
  22. package/dist/hooks/use-password-reset.test.d.ts +2 -0
  23. package/dist/hooks/use-password-reset.test.d.ts.map +1 -0
  24. package/dist/hooks/use-password-reset.test.js +55 -0
  25. package/dist/hooks/use-sign-in.d.ts +15 -0
  26. package/dist/hooks/use-sign-in.d.ts.map +1 -0
  27. package/dist/hooks/use-sign-in.js +80 -0
  28. package/dist/hooks/use-sign-in.test.d.ts +2 -0
  29. package/dist/hooks/use-sign-in.test.d.ts.map +1 -0
  30. package/dist/hooks/use-sign-in.test.js +49 -0
  31. package/dist/hooks/use-sign-up.d.ts +15 -0
  32. package/dist/hooks/use-sign-up.d.ts.map +1 -0
  33. package/dist/hooks/use-sign-up.js +80 -0
  34. package/dist/hooks/use-sign-up.test.d.ts +2 -0
  35. package/dist/hooks/use-sign-up.test.d.ts.map +1 -0
  36. package/dist/hooks/use-sign-up.test.js +49 -0
  37. package/dist/hooks/use-update-account-profile.d.ts +25 -0
  38. package/dist/hooks/use-update-account-profile.d.ts.map +1 -0
  39. package/dist/hooks/use-update-account-profile.js +37 -0
  40. package/dist/hooks/use-update-account-profile.test.d.ts +2 -0
  41. package/dist/hooks/use-update-account-profile.test.d.ts.map +1 -0
  42. package/dist/hooks/use-update-account-profile.test.js +62 -0
  43. package/dist/hooks/use-verify-email.d.ts +18 -0
  44. package/dist/hooks/use-verify-email.d.ts.map +1 -0
  45. package/dist/hooks/use-verify-email.js +86 -0
  46. package/dist/hooks/use-verify-email.test.d.ts +2 -0
  47. package/dist/hooks/use-verify-email.test.d.ts.map +1 -0
  48. package/dist/hooks/use-verify-email.test.js +50 -0
  49. package/dist/hooks/use-workspace-mutation.d.ts +2 -0
  50. package/dist/hooks/use-workspace-mutation.d.ts.map +1 -1
  51. package/dist/query-options.d.ts +8 -0
  52. package/dist/query-options.d.ts.map +1 -1
  53. package/dist/schemas.d.ts +2 -0
  54. package/dist/schemas.d.ts.map +1 -1
  55. package/dist/schemas.js +2 -0
  56. package/package.json +7 -7
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { confirmPasswordReset, requestPasswordReset } from "./use-password-reset.js";
3
+ function jsonResponse(body, init) {
4
+ return new Response(JSON.stringify(body), {
5
+ headers: { "Content-Type": "application/json" },
6
+ ...init,
7
+ });
8
+ }
9
+ describe("password reset helpers", () => {
10
+ it("posts password reset requests to Better Auth", async () => {
11
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({ status: true }));
12
+ const result = await requestPasswordReset({
13
+ email: "ana@example.com",
14
+ redirectTo: "https://operator.example/reset-password",
15
+ }, { baseUrl: "https://operator.example/api", fetcher });
16
+ expect(result).toEqual({ data: { status: true } });
17
+ expect(fetcher).toHaveBeenCalledWith("https://operator.example/api/auth/request-password-reset", {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify({
21
+ email: "ana@example.com",
22
+ redirectTo: "https://operator.example/reset-password",
23
+ }),
24
+ });
25
+ });
26
+ it("posts password reset confirmation tokens to Better Auth", async () => {
27
+ const fixtureResetToken = ["reset", "fixture"].join("-");
28
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({ status: true }));
29
+ const result = await confirmPasswordReset({
30
+ token: fixtureResetToken,
31
+ newPassword: "correct-horse-battery",
32
+ }, { baseUrl: "https://operator.example/api/", fetcher });
33
+ expect(result).toEqual({ data: { status: true } });
34
+ expect(fetcher).toHaveBeenCalledWith("https://operator.example/api/auth/reset-password", {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json" },
37
+ body: JSON.stringify({
38
+ token: fixtureResetToken,
39
+ newPassword: "correct-horse-battery",
40
+ }),
41
+ });
42
+ });
43
+ it("surfaces Better Auth reset errors", async () => {
44
+ const fixtureExpiredToken = ["expired", "fixture"].join("-");
45
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({ error: { message: "Invalid token" } }, {
46
+ status: 400,
47
+ statusText: "Bad Request",
48
+ }));
49
+ await expect(confirmPasswordReset({ token: fixtureExpiredToken, newPassword: "correct-horse-battery" }, { baseUrl: "https://operator.example/api", fetcher })).rejects.toMatchObject({
50
+ name: "VoyantApiError",
51
+ message: "Invalid token",
52
+ status: 400,
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,15 @@
1
+ import { type FetchWithValidationOptions } from "../client.js";
2
+ export interface SignInEmailInput {
3
+ email: string;
4
+ password: string;
5
+ callbackURL?: string;
6
+ rememberMe?: boolean;
7
+ }
8
+ export interface SignInEmailResult {
9
+ data: unknown;
10
+ }
11
+ export declare function signInWithEmail(input: SignInEmailInput, client: FetchWithValidationOptions): Promise<SignInEmailResult>;
12
+ export declare function useSignIn(): {
13
+ email: import("@tanstack/react-query").UseMutationResult<SignInEmailResult, Error, SignInEmailInput, unknown>;
14
+ };
15
+ //# sourceMappingURL=use-sign-in.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sign-in.d.ts","sourceRoot":"","sources":["../../src/hooks/use-sign-in.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,0BAA0B,EAAkB,MAAM,cAAc,CAAA;AAI9E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;CACd;AAwDD,wBAAsB,eAAe,CACnC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAwC5B;AAED,wBAAgB,SAAS;;EAcxB"}
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { VoyantApiError } from "../client.js";
4
+ import { useVoyantAuthContext } from "../provider.js";
5
+ import { authQueryKeys } from "../query-keys.js";
6
+ function joinUrl(baseUrl, path) {
7
+ const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
8
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`;
9
+ return `${trimmedBase}${trimmedPath}`;
10
+ }
11
+ function extractBetterAuthErrorMessage(body, fallback) {
12
+ if (typeof body !== "object" || body === null) {
13
+ return fallback;
14
+ }
15
+ const candidate = body;
16
+ if (typeof candidate.error === "string") {
17
+ return candidate.error;
18
+ }
19
+ if (typeof candidate.error === "object" &&
20
+ candidate.error !== null &&
21
+ "message" in candidate.error) {
22
+ return String(candidate.error.message);
23
+ }
24
+ if (candidate.message !== undefined) {
25
+ return String(candidate.message);
26
+ }
27
+ return fallback;
28
+ }
29
+ function hasBetterAuthError(body) {
30
+ return typeof body === "object" && body !== null && "error" in body;
31
+ }
32
+ async function readJson(response) {
33
+ const text = await response.text();
34
+ if (!text) {
35
+ return undefined;
36
+ }
37
+ try {
38
+ return JSON.parse(text);
39
+ }
40
+ catch {
41
+ return text;
42
+ }
43
+ }
44
+ export async function signInWithEmail(input, client) {
45
+ const response = await client.fetcher(joinUrl(client.baseUrl, "/auth/sign-in/email"), {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ body: JSON.stringify({
49
+ email: input.email,
50
+ password: input.password,
51
+ callbackURL: input.callbackURL,
52
+ rememberMe: input.rememberMe,
53
+ }),
54
+ });
55
+ const body = await readJson(response);
56
+ if (!response.ok || hasBetterAuthError(body)) {
57
+ throw new VoyantApiError(extractBetterAuthErrorMessage(body, `Voyant API error: ${response.status} ${response.statusText}`), response.status, body);
58
+ }
59
+ const statusResponse = await client.fetcher(joinUrl(client.baseUrl, "/auth/status"), {
60
+ method: "GET",
61
+ });
62
+ if (!statusResponse.ok) {
63
+ const statusBody = await readJson(statusResponse);
64
+ throw new VoyantApiError(extractBetterAuthErrorMessage(statusBody, `Voyant API error: ${statusResponse.status} ${statusResponse.statusText}`), statusResponse.status, statusBody);
65
+ }
66
+ return { data: body };
67
+ }
68
+ export function useSignIn() {
69
+ const { baseUrl, fetcher } = useVoyantAuthContext();
70
+ const queryClient = useQueryClient();
71
+ const email = useMutation({
72
+ mutationFn: (input) => signInWithEmail(input, { baseUrl, fetcher }),
73
+ onSuccess: () => {
74
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.authStatus() });
75
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.currentUser() });
76
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.currentWorkspace() });
77
+ },
78
+ });
79
+ return { email };
80
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-sign-in.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sign-in.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-sign-in.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { signInWithEmail } from "./use-sign-in.js";
3
+ function jsonResponse(body, init) {
4
+ return new Response(JSON.stringify(body), {
5
+ headers: { "Content-Type": "application/json" },
6
+ ...init,
7
+ });
8
+ }
9
+ describe("signInWithEmail", () => {
10
+ it("posts credentials to Better Auth and provisions the Voyant profile", async () => {
11
+ const fixturePassword = ["valid", "fixture"].join("-");
12
+ const fetcher = vi
13
+ .fn()
14
+ .mockResolvedValueOnce(jsonResponse({ user: { id: "user_1" } }))
15
+ .mockResolvedValueOnce(jsonResponse({ userExists: true, authenticated: true }));
16
+ const result = await signInWithEmail({
17
+ email: "ana@example.com",
18
+ password: fixturePassword,
19
+ callbackURL: "/bookings",
20
+ rememberMe: true,
21
+ }, { baseUrl: "https://operator.example/api", fetcher });
22
+ expect(result).toEqual({ data: { user: { id: "user_1" } } });
23
+ expect(fetcher).toHaveBeenNthCalledWith(1, "https://operator.example/api/auth/sign-in/email", {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({
27
+ email: "ana@example.com",
28
+ password: fixturePassword,
29
+ callbackURL: "/bookings",
30
+ rememberMe: true,
31
+ }),
32
+ });
33
+ expect(fetcher).toHaveBeenNthCalledWith(2, "https://operator.example/api/auth/status", {
34
+ method: "GET",
35
+ });
36
+ });
37
+ it("surfaces Better Auth error messages", async () => {
38
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({ error: { message: "Email is not verified" } }, {
39
+ status: 403,
40
+ statusText: "Forbidden",
41
+ }));
42
+ await expect(signInWithEmail({ email: "ana@example.com", password: "wrong" }, { baseUrl: "https://operator.example/api/", fetcher })).rejects.toMatchObject({
43
+ name: "VoyantApiError",
44
+ message: "Email is not verified",
45
+ status: 403,
46
+ });
47
+ expect(fetcher).toHaveBeenCalledTimes(1);
48
+ });
49
+ });
@@ -0,0 +1,15 @@
1
+ import { type FetchWithValidationOptions } from "../client.js";
2
+ export interface SignUpEmailInput {
3
+ name: string;
4
+ email: string;
5
+ password: string;
6
+ callbackURL?: string;
7
+ }
8
+ export interface SignUpEmailResult {
9
+ data: unknown;
10
+ }
11
+ export declare function signUpWithEmail(input: SignUpEmailInput, client: FetchWithValidationOptions): Promise<SignUpEmailResult>;
12
+ export declare function useSignUp(): {
13
+ email: import("@tanstack/react-query").UseMutationResult<SignUpEmailResult, Error, SignUpEmailInput, unknown>;
14
+ };
15
+ //# sourceMappingURL=use-sign-up.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sign-up.d.ts","sourceRoot":"","sources":["../../src/hooks/use-sign-up.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,0BAA0B,EAAkB,MAAM,cAAc,CAAA;AAI9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;CACd;AAwDD,wBAAsB,eAAe,CACnC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAwC5B;AAED,wBAAgB,SAAS;;EAcxB"}
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { VoyantApiError } from "../client.js";
4
+ import { useVoyantAuthContext } from "../provider.js";
5
+ import { authQueryKeys } from "../query-keys.js";
6
+ function joinUrl(baseUrl, path) {
7
+ const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
8
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`;
9
+ return `${trimmedBase}${trimmedPath}`;
10
+ }
11
+ function extractBetterAuthErrorMessage(body, fallback) {
12
+ if (typeof body !== "object" || body === null) {
13
+ return fallback;
14
+ }
15
+ const candidate = body;
16
+ if (typeof candidate.error === "string") {
17
+ return candidate.error;
18
+ }
19
+ if (typeof candidate.error === "object" &&
20
+ candidate.error !== null &&
21
+ "message" in candidate.error) {
22
+ return String(candidate.error.message);
23
+ }
24
+ if (candidate.message !== undefined) {
25
+ return String(candidate.message);
26
+ }
27
+ return fallback;
28
+ }
29
+ function hasBetterAuthError(body) {
30
+ return typeof body === "object" && body !== null && "error" in body;
31
+ }
32
+ async function readJson(response) {
33
+ const text = await response.text();
34
+ if (!text) {
35
+ return undefined;
36
+ }
37
+ try {
38
+ return JSON.parse(text);
39
+ }
40
+ catch {
41
+ return text;
42
+ }
43
+ }
44
+ export async function signUpWithEmail(input, client) {
45
+ const response = await client.fetcher(joinUrl(client.baseUrl, "/auth/sign-up/email"), {
46
+ method: "POST",
47
+ headers: { "Content-Type": "application/json" },
48
+ body: JSON.stringify({
49
+ name: input.name,
50
+ email: input.email,
51
+ password: input.password,
52
+ callbackURL: input.callbackURL,
53
+ }),
54
+ });
55
+ const body = await readJson(response);
56
+ if (!response.ok || hasBetterAuthError(body)) {
57
+ throw new VoyantApiError(extractBetterAuthErrorMessage(body, `Voyant API error: ${response.status} ${response.statusText}`), response.status, body);
58
+ }
59
+ const statusResponse = await client.fetcher(joinUrl(client.baseUrl, "/auth/status"), {
60
+ method: "GET",
61
+ });
62
+ if (!statusResponse.ok) {
63
+ const statusBody = await readJson(statusResponse);
64
+ throw new VoyantApiError(extractBetterAuthErrorMessage(statusBody, `Voyant API error: ${statusResponse.status} ${statusResponse.statusText}`), statusResponse.status, statusBody);
65
+ }
66
+ return { data: body };
67
+ }
68
+ export function useSignUp() {
69
+ const { baseUrl, fetcher } = useVoyantAuthContext();
70
+ const queryClient = useQueryClient();
71
+ const email = useMutation({
72
+ mutationFn: (input) => signUpWithEmail(input, { baseUrl, fetcher }),
73
+ onSuccess: () => {
74
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.authStatus() });
75
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.currentUser() });
76
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.currentWorkspace() });
77
+ },
78
+ });
79
+ return { email };
80
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-sign-up.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-sign-up.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-sign-up.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { signUpWithEmail } from "./use-sign-up.js";
3
+ function jsonResponse(body, init) {
4
+ return new Response(JSON.stringify(body), {
5
+ headers: { "Content-Type": "application/json" },
6
+ ...init,
7
+ });
8
+ }
9
+ describe("signUpWithEmail", () => {
10
+ it("posts account details to Better Auth", async () => {
11
+ const fixturePassword = ["valid", "fixture"].join("-");
12
+ const fetcher = vi
13
+ .fn()
14
+ .mockResolvedValueOnce(jsonResponse({ user: { id: "user_1" } }))
15
+ .mockResolvedValueOnce(jsonResponse({ userExists: true, authenticated: true }));
16
+ const result = await signUpWithEmail({
17
+ name: "Ana Voyant",
18
+ email: "ana@example.com",
19
+ password: fixturePassword,
20
+ callbackURL: "/",
21
+ }, { baseUrl: "https://operator.example/api", fetcher });
22
+ expect(result).toEqual({ data: { user: { id: "user_1" } } });
23
+ expect(fetcher).toHaveBeenNthCalledWith(1, "https://operator.example/api/auth/sign-up/email", {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({
27
+ name: "Ana Voyant",
28
+ email: "ana@example.com",
29
+ password: fixturePassword,
30
+ callbackURL: "/",
31
+ }),
32
+ });
33
+ expect(fetcher).toHaveBeenNthCalledWith(2, "https://operator.example/api/auth/status", {
34
+ method: "GET",
35
+ });
36
+ });
37
+ it("surfaces Better Auth sign-up errors", async () => {
38
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({ error: "Sign-up is disabled. Ask an admin to invite you." }, {
39
+ status: 403,
40
+ statusText: "Forbidden",
41
+ }));
42
+ await expect(signUpWithEmail({ name: "Ana Voyant", email: "ana@example.com", password: "wrong" }, { baseUrl: "https://operator.example/api/", fetcher })).rejects.toMatchObject({
43
+ name: "VoyantApiError",
44
+ message: "Sign-up is disabled. Ask an admin to invite you.",
45
+ status: 403,
46
+ });
47
+ expect(fetcher).toHaveBeenCalledTimes(1);
48
+ });
49
+ });
@@ -0,0 +1,25 @@
1
+ import { type FetchWithValidationOptions } from "../client.js";
2
+ import { type CurrentUser } from "../schemas.js";
3
+ export interface UpdateAccountProfileInput {
4
+ firstName?: string | null;
5
+ lastName?: string | null;
6
+ locale?: string | null;
7
+ timezone?: string | null;
8
+ profilePictureUrl?: string | null;
9
+ }
10
+ export type UpdateAccountProfileResult = CurrentUser;
11
+ export declare function updateAccountProfile(input: UpdateAccountProfileInput, client: FetchWithValidationOptions): Promise<UpdateAccountProfileResult>;
12
+ export declare function useUpdateAccountProfile(): import("@tanstack/react-query").UseMutationResult<{
13
+ id: string;
14
+ email: string | null;
15
+ firstName: string | null;
16
+ lastName: string | null;
17
+ locale: string;
18
+ timezone: string | null;
19
+ isSuperAdmin: boolean;
20
+ isSupportUser: boolean;
21
+ createdAt: string;
22
+ phoneNumber?: string | null | undefined;
23
+ profilePictureUrl?: string | null | undefined;
24
+ }, Error, UpdateAccountProfileInput, unknown>;
25
+ //# sourceMappingURL=use-update-account-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-update-account-profile.d.ts","sourceRoot":"","sources":["../../src/hooks/use-update-account-profile.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,0BAA0B,EAAuB,MAAM,cAAc,CAAA;AAGnF,OAAO,EAAE,KAAK,WAAW,EAAqB,MAAM,eAAe,CAAA;AAEnE,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,MAAM,0BAA0B,GAAG,WAAW,CAAA;AAEpD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,EAChC,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,0BAA0B,CAAC,CAKrC;AAED,wBAAgB,uBAAuB;;;;;;;;;;;;8CAYtC"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { fetchWithValidation } from "../client.js";
4
+ import { useVoyantAuthContext } from "../provider.js";
5
+ import { authQueryKeys } from "../query-keys.js";
6
+ import { currentUserSchema } from "../schemas.js";
7
+ export async function updateAccountProfile(input, client) {
8
+ return fetchWithValidation("/auth/me", currentUserSchema, client, {
9
+ method: "PATCH",
10
+ body: JSON.stringify(profilePatchBody(input)),
11
+ });
12
+ }
13
+ export function useUpdateAccountProfile() {
14
+ const { baseUrl, fetcher } = useVoyantAuthContext();
15
+ const queryClient = useQueryClient();
16
+ return useMutation({
17
+ mutationFn: (input) => updateAccountProfile(input, { baseUrl, fetcher }),
18
+ onSuccess: (user) => {
19
+ queryClient.setQueryData(authQueryKeys.currentUser(), user);
20
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.authStatus() });
21
+ },
22
+ });
23
+ }
24
+ function profilePatchBody(input) {
25
+ const body = {};
26
+ if ("firstName" in input)
27
+ body.firstName = input.firstName;
28
+ if ("lastName" in input)
29
+ body.lastName = input.lastName;
30
+ if ("locale" in input)
31
+ body.locale = input.locale;
32
+ if ("timezone" in input)
33
+ body.timezone = input.timezone;
34
+ if ("profilePictureUrl" in input)
35
+ body.profilePictureUrl = input.profilePictureUrl;
36
+ return body;
37
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-update-account-profile.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-update-account-profile.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-update-account-profile.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { updateAccountProfile } from "./use-update-account-profile.js";
3
+ function jsonResponse(body, init) {
4
+ return new Response(JSON.stringify(body), {
5
+ headers: { "Content-Type": "application/json" },
6
+ ...init,
7
+ });
8
+ }
9
+ describe("updateAccountProfile", () => {
10
+ it("patches the current user's profile fields", async () => {
11
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({
12
+ id: "user_1",
13
+ email: "ana@example.com",
14
+ phoneNumber: null,
15
+ firstName: "Ana",
16
+ lastName: "Pop",
17
+ locale: "ro",
18
+ timezone: "Europe/Bucharest",
19
+ isSuperAdmin: false,
20
+ isSupportUser: false,
21
+ createdAt: "2026-05-12T00:00:00.000Z",
22
+ profilePictureUrl: null,
23
+ }));
24
+ const result = await updateAccountProfile({
25
+ firstName: "Ana",
26
+ lastName: "Pop",
27
+ locale: "ro",
28
+ timezone: "Europe/Bucharest",
29
+ }, { baseUrl: "https://operator.example/api", fetcher });
30
+ expect(result.firstName).toBe("Ana");
31
+ expect(result.locale).toBe("ro");
32
+ expect(fetcher).toHaveBeenCalledWith("https://operator.example/api/auth/me", {
33
+ method: "PATCH",
34
+ headers: new Headers({ "Content-Type": "application/json" }),
35
+ body: JSON.stringify({
36
+ firstName: "Ana",
37
+ lastName: "Pop",
38
+ locale: "ro",
39
+ timezone: "Europe/Bucharest",
40
+ }),
41
+ });
42
+ });
43
+ it("preserves explicit null fields in the patch body", async () => {
44
+ const fetcher = vi.fn().mockResolvedValueOnce(jsonResponse({
45
+ id: "user_1",
46
+ email: "ana@example.com",
47
+ firstName: null,
48
+ lastName: null,
49
+ locale: "en",
50
+ timezone: null,
51
+ isSuperAdmin: false,
52
+ isSupportUser: false,
53
+ createdAt: "2026-05-12T00:00:00.000Z",
54
+ }));
55
+ await updateAccountProfile({ firstName: null, lastName: null, timezone: null }, { baseUrl: "https://operator.example/api/", fetcher });
56
+ expect(fetcher).toHaveBeenCalledWith("https://operator.example/api/auth/me", {
57
+ method: "PATCH",
58
+ headers: new Headers({ "Content-Type": "application/json" }),
59
+ body: JSON.stringify({ firstName: null, lastName: null, timezone: null }),
60
+ });
61
+ });
62
+ });
@@ -0,0 +1,18 @@
1
+ import { type FetchWithValidationOptions } from "../client.js";
2
+ export interface VerifyEmailTokenInput {
3
+ token: string;
4
+ email?: never;
5
+ otp?: never;
6
+ }
7
+ export interface VerifyEmailOtpInput {
8
+ email: string;
9
+ otp: string;
10
+ token?: never;
11
+ }
12
+ export type VerifyEmailInput = VerifyEmailTokenInput | VerifyEmailOtpInput;
13
+ export interface VerifyEmailResult {
14
+ data: unknown;
15
+ }
16
+ export declare function verifyEmail(input: VerifyEmailInput, client: FetchWithValidationOptions): Promise<VerifyEmailResult>;
17
+ export declare function useVerifyEmail(): import("@tanstack/react-query").UseMutationResult<VerifyEmailResult, Error, VerifyEmailInput, unknown>;
18
+ //# sourceMappingURL=use-verify-email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-verify-email.d.ts","sourceRoot":"","sources":["../../src/hooks/use-verify-email.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,0BAA0B,EAAmC,MAAM,cAAc,CAAA;AAI/F,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,GAAG,CAAC,EAAE,KAAK,CAAA;CACZ;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAAG,mBAAmB,CAAA;AAE1E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;CACd;AAgFD,wBAAsB,WAAW,CAC/B,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAuB5B;AAED,wBAAgB,cAAc,2GAY7B"}
@@ -0,0 +1,86 @@
1
+ "use client";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import { VoyantApiError, withQueryParams } from "../client.js";
4
+ import { useVoyantAuthContext } from "../provider.js";
5
+ import { authQueryKeys } from "../query-keys.js";
6
+ function joinUrl(baseUrl, path) {
7
+ const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
8
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`;
9
+ return `${trimmedBase}${trimmedPath}`;
10
+ }
11
+ function extractBetterAuthErrorMessage(body, fallback) {
12
+ if (typeof body !== "object" || body === null) {
13
+ return fallback;
14
+ }
15
+ const candidate = body;
16
+ if (typeof candidate.error === "string") {
17
+ return candidate.error;
18
+ }
19
+ if (typeof candidate.error === "object" &&
20
+ candidate.error !== null &&
21
+ "message" in candidate.error) {
22
+ return String(candidate.error.message);
23
+ }
24
+ if (candidate.message !== undefined) {
25
+ return String(candidate.message);
26
+ }
27
+ return fallback;
28
+ }
29
+ function hasBetterAuthError(body) {
30
+ if (typeof body !== "object" || body === null || !("error" in body)) {
31
+ return false;
32
+ }
33
+ return body.error != null;
34
+ }
35
+ async function readJson(response) {
36
+ const text = await response.text();
37
+ if (!text) {
38
+ return undefined;
39
+ }
40
+ try {
41
+ return JSON.parse(text);
42
+ }
43
+ catch {
44
+ return text;
45
+ }
46
+ }
47
+ function isOtpInput(input) {
48
+ return "otp" in input;
49
+ }
50
+ async function assertOkResponse(response) {
51
+ const body = await readJson(response);
52
+ if (!response.ok || hasBetterAuthError(body)) {
53
+ throw new VoyantApiError(extractBetterAuthErrorMessage(body, `Voyant API error: ${response.status} ${response.statusText}`), response.status, body);
54
+ }
55
+ return body;
56
+ }
57
+ export async function verifyEmail(input, client) {
58
+ const response = isOtpInput(input)
59
+ ? await client.fetcher(joinUrl(client.baseUrl, "/auth/email-otp/verify-email"), {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json" },
62
+ body: JSON.stringify({
63
+ email: input.email,
64
+ otp: input.otp,
65
+ }),
66
+ })
67
+ : await client.fetcher(joinUrl(client.baseUrl, withQueryParams("/auth/verify-email", { token: input.token })), { method: "GET" });
68
+ const body = await assertOkResponse(response);
69
+ const statusResponse = await client.fetcher(joinUrl(client.baseUrl, "/auth/status"), {
70
+ method: "GET",
71
+ });
72
+ await assertOkResponse(statusResponse);
73
+ return { data: body };
74
+ }
75
+ export function useVerifyEmail() {
76
+ const { baseUrl, fetcher } = useVoyantAuthContext();
77
+ const queryClient = useQueryClient();
78
+ return useMutation({
79
+ mutationFn: (input) => verifyEmail(input, { baseUrl, fetcher }),
80
+ onSuccess: () => {
81
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.authStatus() });
82
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.currentUser() });
83
+ void queryClient.invalidateQueries({ queryKey: authQueryKeys.currentWorkspace() });
84
+ },
85
+ });
86
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-verify-email.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-verify-email.test.d.ts","sourceRoot":"","sources":["../../src/hooks/use-verify-email.test.ts"],"names":[],"mappings":""}