@burdenoff/microfe-auth 2026.519.1 → 2026.519.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"usePasswordOperations.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/usePasswordOperations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAoChF;;;GAGG;AACH,wBAAgB,qBAAqB;4BAqB1B,mBAAmB,KACzB,OAAO,CAAC,OAAO,CAAC;kCAsBV,MAAM,mBACI,MAAM,KACtB,OAAO,CAAC,0BAA0B,CAAC;2BAsB7B,MAAM,eACA,MAAM,KAClB,OAAO,CAAC,OAAO,CAAC;;EAwBpB"}
1
+ {"version":3,"file":"usePasswordOperations.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/usePasswordOperations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAoChF;;;GAGG;AACH,wBAAgB,qBAAqB;4BAqB1B,mBAAmB,KACzB,OAAO,CAAC,OAAO,CAAC;kCAsBV,MAAM,mBACI,MAAM,KACtB,OAAO,CAAC,0BAA0B,CAAC;2BAsB7B,MAAM,eACA,MAAM,KAClB,OAAO,CAAC,OAAO,CAAC;;EA2DpB"}
@@ -36,13 +36,20 @@ function a() {
36
36
  let r = e.getState();
37
37
  try {
38
38
  r.setLoading(!0);
39
- let { data: e } = await c({ variables: {
39
+ let e = await c({ variables: {
40
40
  token: t,
41
41
  newPassword: n
42
- } });
43
- return e.resetPassword;
42
+ } }), i = e?.error;
43
+ if (i) {
44
+ let e = i.errors?.[0]?.extensions?.code;
45
+ throw Error(e === "TOKEN_EXPIRED" ? "TOKEN_EXPIRED" : e === "INVALID_TOKEN" ? "INVALID_TOKEN" : i.errors?.[0]?.message ?? "RESET_FAILED");
46
+ }
47
+ return e.data.resetPassword;
44
48
  } catch (e) {
45
- throw console.error("Failed to reset password:", e), e;
49
+ let t = e?.message ?? "";
50
+ if (t === "TOKEN_EXPIRED" || t === "INVALID_TOKEN") throw e;
51
+ let n = (e?.errors?.[0] ?? e?.graphQLErrors?.[0])?.extensions?.code;
52
+ throw n === "TOKEN_EXPIRED" ? Error("TOKEN_EXPIRED") : n === "INVALID_TOKEN" ? Error("INVALID_TOKEN") : e;
46
53
  } finally {
47
54
  r.setLoading(!1);
48
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"usePasswordOperations.js","names":[],"sources":["../../../src/auth/hooks/usePasswordOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport type { ChangePasswordInput, PasswordResetRequestResult } from \"../types\";\n\nimport {\n ChangePasswordDocument,\n RequestPasswordResetDocument,\n ResetPasswordDocument,\n} from \"../../generated/global-operations\";\n// Apollo Client 4.x Response Interfaces\ninterface ChangePasswordResponse {\n changePassword: boolean;\n}\n\ninterface ChangePasswordVariables {\n input: ChangePasswordInput;\n}\n\ninterface RequestPasswordResetResponse {\n requestPasswordReset: PasswordResetRequestResult;\n}\n\ninterface RequestPasswordResetVariables {\n email: string;\n recaptchaToken?: string;\n}\n\ninterface ResetPasswordResponse {\n resetPassword: boolean;\n}\n\ninterface ResetPasswordVariables {\n token: string;\n newPassword: string;\n}\n\n// GraphQL Operations\n\n/**\n * Hook for password management operations\n * Uses Zustand v5 pattern: selectors for state, getState() for actions\n */\nexport function usePasswordOperations() {\n // Use selectors for reactive state\n const isLoading = useAuthStore((state) => state.isLoading);\n\n const [changePasswordMutation] = useMutation<\n ChangePasswordResponse,\n ChangePasswordVariables\n >(ChangePasswordDocument);\n const [requestPasswordResetMutation] = useMutation<\n RequestPasswordResetResponse,\n RequestPasswordResetVariables\n >(RequestPasswordResetDocument);\n const [resetPasswordMutation] = useMutation<\n ResetPasswordResponse,\n ResetPasswordVariables\n >(ResetPasswordDocument);\n\n /**\n * Change password for authenticated user\n */\n const changePassword = async (\n input: ChangePasswordInput,\n ): Promise<boolean> => {\n const store = useAuthStore.getState();\n try {\n store.setLoading(true);\n\n const { data } = await changePasswordMutation({\n variables: { input },\n });\n\n return data!.changePassword;\n } catch (error) {\n console.error(\"Failed to change password:\", error);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Request password reset email\n */\n const requestPasswordReset = async (\n email: string,\n recaptchaToken?: string,\n ): Promise<PasswordResetRequestResult> => {\n const store = useAuthStore.getState();\n try {\n store.setLoading(true);\n\n const { data } = await requestPasswordResetMutation({\n variables: { email, ...(recaptchaToken ? { recaptchaToken } : {}) },\n });\n\n return data!.requestPasswordReset;\n } catch (error) {\n console.error(\"Failed to request password reset:\", error);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Reset password with token\n */\n const resetPassword = async (\n token: string,\n newPassword: string,\n ): Promise<boolean> => {\n const store = useAuthStore.getState();\n try {\n store.setLoading(true);\n\n const { data } = await resetPasswordMutation({\n variables: { token, newPassword },\n });\n\n return data!.resetPassword;\n } catch (error) {\n console.error(\"Failed to reset password:\", error);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n return {\n changePassword,\n requestPasswordReset,\n resetPassword,\n isLoading,\n };\n}\n"],"mappings":";;;;AA0CA,SAAgB,IAAwB;CAEtC,IAAM,IAAY,GAAc,MAAU,EAAM,UAAU,EAEpD,CAAC,KAA0B,EAG/B,EAAuB,EACnB,CAAC,KAAgC,EAGrC,EAA6B,EACzB,CAAC,KAAyB,EAG9B,EAAsB;AAyExB,QAAO;EACL,gBArEqB,OACrB,MACqB;GACrB,IAAM,IAAQ,EAAa,UAAU;AACrC,OAAI;AACF,MAAM,WAAW,GAAK;IAEtB,IAAM,EAAE,YAAS,MAAM,EAAuB,EAC5C,WAAW,EAAE,UAAO,EACrB,CAAC;AAEF,WAAO,EAAM;YACN,GAAO;AAEd,UADA,QAAQ,MAAM,8BAA8B,EAAM,EAC5C;aACE;AACR,MAAM,WAAW,GAAM;;;EAsDzB,sBA/C2B,OAC3B,GACA,MACwC;GACxC,IAAM,IAAQ,EAAa,UAAU;AACrC,OAAI;AACF,MAAM,WAAW,GAAK;IAEtB,IAAM,EAAE,YAAS,MAAM,EAA6B,EAClD,WAAW;KAAE;KAAO,GAAI,IAAiB,EAAE,mBAAgB,GAAG,EAAE;KAAG,EACpE,CAAC;AAEF,WAAO,EAAM;YACN,GAAO;AAEd,UADA,QAAQ,MAAM,qCAAqC,EAAM,EACnD;aACE;AACR,MAAM,WAAW,GAAM;;;EA+BzB,eAxBoB,OACpB,GACA,MACqB;GACrB,IAAM,IAAQ,EAAa,UAAU;AACrC,OAAI;AACF,MAAM,WAAW,GAAK;IAEtB,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW;KAAE;KAAO;KAAa,EAClC,CAAC;AAEF,WAAO,EAAM;YACN,GAAO;AAEd,UADA,QAAQ,MAAM,6BAA6B,EAAM,EAC3C;aACE;AACR,MAAM,WAAW,GAAM;;;EAQzB;EACD"}
1
+ {"version":3,"file":"usePasswordOperations.js","names":[],"sources":["../../../src/auth/hooks/usePasswordOperations.ts"],"sourcesContent":["import { useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport type { ChangePasswordInput, PasswordResetRequestResult } from \"../types\";\n\nimport {\n ChangePasswordDocument,\n RequestPasswordResetDocument,\n ResetPasswordDocument,\n} from \"../../generated/global-operations\";\n// Apollo Client 4.x Response Interfaces\ninterface ChangePasswordResponse {\n changePassword: boolean;\n}\n\ninterface ChangePasswordVariables {\n input: ChangePasswordInput;\n}\n\ninterface RequestPasswordResetResponse {\n requestPasswordReset: PasswordResetRequestResult;\n}\n\ninterface RequestPasswordResetVariables {\n email: string;\n recaptchaToken?: string;\n}\n\ninterface ResetPasswordResponse {\n resetPassword: boolean;\n}\n\ninterface ResetPasswordVariables {\n token: string;\n newPassword: string;\n}\n\n// GraphQL Operations\n\n/**\n * Hook for password management operations\n * Uses Zustand v5 pattern: selectors for state, getState() for actions\n */\nexport function usePasswordOperations() {\n // Use selectors for reactive state\n const isLoading = useAuthStore((state) => state.isLoading);\n\n const [changePasswordMutation] = useMutation<\n ChangePasswordResponse,\n ChangePasswordVariables\n >(ChangePasswordDocument);\n const [requestPasswordResetMutation] = useMutation<\n RequestPasswordResetResponse,\n RequestPasswordResetVariables\n >(RequestPasswordResetDocument);\n const [resetPasswordMutation] = useMutation<\n ResetPasswordResponse,\n ResetPasswordVariables\n >(ResetPasswordDocument);\n\n /**\n * Change password for authenticated user\n */\n const changePassword = async (\n input: ChangePasswordInput,\n ): Promise<boolean> => {\n const store = useAuthStore.getState();\n try {\n store.setLoading(true);\n\n const { data } = await changePasswordMutation({\n variables: { input },\n });\n\n return data!.changePassword;\n } catch (error) {\n console.error(\"Failed to change password:\", error);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Request password reset email\n */\n const requestPasswordReset = async (\n email: string,\n recaptchaToken?: string,\n ): Promise<PasswordResetRequestResult> => {\n const store = useAuthStore.getState();\n try {\n store.setLoading(true);\n\n const { data } = await requestPasswordResetMutation({\n variables: { email, ...(recaptchaToken ? { recaptchaToken } : {}) },\n });\n\n return data!.requestPasswordReset;\n } catch (error) {\n console.error(\"Failed to request password reset:\", error);\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n /**\n * Reset password with token\n */\n const resetPassword = async (\n token: string,\n newPassword: string,\n ): Promise<boolean> => {\n const store = useAuthStore.getState();\n try {\n store.setLoading(true);\n\n type GqlErrorLike = {\n extensions?: { code?: string };\n message: string;\n };\n\n // Apollo Client 4 resolves (rather than throws) when the server returns\n // GraphQL errors. The error lives in result.error (a CombinedGraphQLErrors\n // with an `.errors` array). We inspect it before touching `data` to avoid\n // a TypeError from `null.resetPassword`.\n const result = await resetPasswordMutation({\n variables: { token, newPassword },\n });\n\n // AC4: check result.error first\n const resultError = (\n result as unknown as { error?: { errors?: GqlErrorLike[] } }\n )?.error;\n if (resultError) {\n const code = resultError.errors?.[0]?.extensions?.code;\n if (code === \"TOKEN_EXPIRED\") throw new Error(\"TOKEN_EXPIRED\");\n if (code === \"INVALID_TOKEN\") throw new Error(\"INVALID_TOKEN\");\n throw new Error(resultError.errors?.[0]?.message ?? \"RESET_FAILED\");\n }\n\n return result.data!.resetPassword;\n } catch (error: unknown) {\n // Re-throw sentinel errors unchanged; for anything else apply a final\n // AC3-style graphQLErrors fallback before surfacing as generic.\n const sentinelMsg = (error as Error)?.message ?? \"\";\n if (sentinelMsg === \"TOKEN_EXPIRED\" || sentinelMsg === \"INVALID_TOKEN\") {\n throw error;\n }\n type GqlErrorLike = {\n extensions?: { code?: string };\n message: string;\n };\n const gqlError: GqlErrorLike | undefined =\n (error as { errors?: GqlErrorLike[] })?.errors?.[0] ??\n (error as { graphQLErrors?: GqlErrorLike[] })?.graphQLErrors?.[0];\n const code = gqlError?.extensions?.code;\n if (code === \"TOKEN_EXPIRED\") throw new Error(\"TOKEN_EXPIRED\");\n if (code === \"INVALID_TOKEN\") throw new Error(\"INVALID_TOKEN\");\n throw error;\n } finally {\n store.setLoading(false);\n }\n };\n\n return {\n changePassword,\n requestPasswordReset,\n resetPassword,\n isLoading,\n };\n}\n"],"mappings":";;;;AA0CA,SAAgB,IAAwB;CAEtC,IAAM,IAAY,GAAc,MAAU,EAAM,UAAU,EAEpD,CAAC,KAA0B,EAG/B,EAAuB,EACnB,CAAC,KAAgC,EAGrC,EAA6B,EACzB,CAAC,KAAyB,EAG9B,EAAsB;AA4GxB,QAAO;EACL,gBAxGqB,OACrB,MACqB;GACrB,IAAM,IAAQ,EAAa,UAAU;AACrC,OAAI;AACF,MAAM,WAAW,GAAK;IAEtB,IAAM,EAAE,YAAS,MAAM,EAAuB,EAC5C,WAAW,EAAE,UAAO,EACrB,CAAC;AAEF,WAAO,EAAM;YACN,GAAO;AAEd,UADA,QAAQ,MAAM,8BAA8B,EAAM,EAC5C;aACE;AACR,MAAM,WAAW,GAAM;;;EAyFzB,sBAlF2B,OAC3B,GACA,MACwC;GACxC,IAAM,IAAQ,EAAa,UAAU;AACrC,OAAI;AACF,MAAM,WAAW,GAAK;IAEtB,IAAM,EAAE,YAAS,MAAM,EAA6B,EAClD,WAAW;KAAE;KAAO,GAAI,IAAiB,EAAE,mBAAgB,GAAG,EAAE;KAAG,EACpE,CAAC;AAEF,WAAO,EAAM;YACN,GAAO;AAEd,UADA,QAAQ,MAAM,qCAAqC,EAAM,EACnD;aACE;AACR,MAAM,WAAW,GAAM;;;EAkEzB,eA3DoB,OACpB,GACA,MACqB;GACrB,IAAM,IAAQ,EAAa,UAAU;AACrC,OAAI;AACF,MAAM,WAAW,GAAK;IAWtB,IAAM,IAAS,MAAM,EAAsB,EACzC,WAAW;KAAE;KAAO;KAAa,EAClC,CAAC,EAGI,IACJ,GACC;AACH,QAAI,GAAa;KACf,IAAM,IAAO,EAAY,SAAS,IAAI,YAAY;AAGlD,WAFwC,MAApC,MAAS,kBAAiC,kBAC1C,MAAS,kBAAiC,kBAC9B,EAAY,SAAS,IAAI,WAAW,eAFU;;AAKhE,WAAO,EAAO,KAAM;YACb,GAAgB;IAGvB,IAAM,IAAe,GAAiB,WAAW;AACjD,QAAI,MAAgB,mBAAmB,MAAgB,gBACrD,OAAM;IASR,IAAM,KAFH,GAAuC,SAAS,MAChD,GAA8C,gBAAgB,KAC1C,YAAY;AAGnC,UAFI,MAAS,kBAA2B,MAAM,gBAAgB,GAC1D,MAAS,kBAA2B,MAAM,gBAAgB,GACxD;aACE;AACR,MAAM,WAAW,GAAM;;;EAQzB;EACD"}
@@ -6,6 +6,8 @@ interface TokenInfoQueryResponse {
6
6
  }
7
7
  interface UseSessionOperationsOptions {
8
8
  disableTokenInfoQuery?: boolean;
9
+ /** Number of sessions to load per page (default: 10) */
10
+ pageSize?: number;
9
11
  }
10
12
  /**
11
13
  * Hook for session/token management operations
@@ -33,7 +35,16 @@ export declare function useSessionOperations(options?: UseSessionOperationsOptio
33
35
  isCurrent: boolean;
34
36
  }[];
35
37
  sessionsLoading: boolean;
36
- refetchSessions: (variables?: Partial<import('@apollo/client').OperationVariables> | undefined) => Promise<import("@apollo/client").ApolloClient.QueryResult<ActiveSessionsQuery>>;
38
+ refetchSessions: (variables?: Partial<import('../../generated/global-types').Exact<{
39
+ pagination?: import('../../generated/global-types').InputMaybe<import('../../generated/global-types').PaginationInput>;
40
+ }>> | undefined) => Promise<import("@apollo/client").ApolloClient.QueryResult<ActiveSessionsQuery>>;
41
+ page: number;
42
+ pageSize: number;
43
+ totalCount: number;
44
+ totalPages: number;
45
+ hasMore: boolean;
46
+ goToNextPage: () => void;
47
+ goToPrevPage: () => void;
37
48
  isLoading: boolean;
38
49
  refetch: (variables?: Partial<import('@apollo/client').OperationVariables> | undefined) => Promise<import("@apollo/client").ApolloClient.QueryResult<TokenInfoQueryResponse>>;
39
50
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useSessionOperations.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/useSessionOperations.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,mBAAmB,EAKpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAM7E,UAAU,sBAAsB;IAC9B,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAUD,UAAU,2BAA2B;IACnC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC;4BAoDhC,mBAAmB,KACzB,OAAO,CAAC,uBAAuB,CAAC;0BAaR,OAAO;iCAQA,MAAM;;+BASA,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;6BAmB7B,OAAO,CAAC;QAC1C,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;;;;;;;;;;;;;;;;;EA8BH"}
1
+ {"version":3,"file":"useSessionOperations.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/useSessionOperations.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,mBAAmB,EAMpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAM7E,UAAU,sBAAsB;IAC9B,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAUD,UAAU,2BAA2B;IACnC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,2BAAgC;4BAkEhC,mBAAmB,KACzB,OAAO,CAAC,uBAAuB,CAAC;0BAaR,OAAO;iCAQA,MAAM;;+BASA,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;6BAmB7B,OAAO,CAAC;QAC1C,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;EAsDH"}
@@ -1,60 +1,76 @@
1
1
  import { useAuthStore as e } from "../store/authStore.js";
2
2
  import { ActiveSessionsDocument as t, RevokeAllSessionsDocument as n, RevokeSessionDocument as r, TokenInfoDocument as i, ValidateTokensDocument as a } from "../../generated/global-operations.js";
3
- import { useMutation as o, useQuery as s } from "@apollo/client/react";
3
+ import { useState as o } from "react";
4
+ import { useMutation as s, useQuery as c } from "@apollo/client/react";
4
5
  //#region src/auth/hooks/useSessionOperations.ts
5
- function c(c = {}) {
6
- let l = e(), u = c.disableTokenInfoQuery ?? !1, [d] = o(a), { data: f, loading: p, refetch: m } = s(i, {
7
- skip: u || !l.isAuthenticated,
6
+ function l(l = {}) {
7
+ let u = e(), d = l.disableTokenInfoQuery ?? !1, f = l.pageSize ?? 10, [p, m] = o(0), [h] = s(a), { data: g, loading: _, refetch: v } = c(i, {
8
+ skip: d || !u.isAuthenticated,
8
9
  fetchPolicy: "cache-and-network",
9
10
  pollInterval: 300 * 1e3
10
- }), { data: h, loading: g, refetch: _ } = s(t, {
11
- skip: !l.isAuthenticated,
11
+ }), { data: y, loading: b, refetch: x } = c(t, {
12
+ skip: !u.isAuthenticated,
12
13
  fetchPolicy: "cache-and-network",
13
- context: l.accessToken ? { headers: { authorization: `Bearer ${l.accessToken}` } } : void 0
14
- }), [v] = o(r), [y] = o(n);
14
+ variables: { pagination: {
15
+ limit: f,
16
+ offset: p * f
17
+ } },
18
+ context: u.accessToken ? { headers: { authorization: `Bearer ${u.accessToken}` } } : void 0
19
+ }), [S] = s(r), [C] = s(n), w = async (e) => {
20
+ try {
21
+ let { data: t } = await h({ variables: { input: e } });
22
+ return t.validateTokens;
23
+ } catch (e) {
24
+ throw console.error("Failed to validate tokens:", e), e;
25
+ }
26
+ }, T = () => g?.tokenInfo?.expiresAt ? Date.now() / 1e3 > g.tokenInfo.expiresAt : !0, E = () => {
27
+ if (!g?.tokenInfo?.expiresAt) return 0;
28
+ let e = g.tokenInfo.expiresAt - Date.now() / 1e3;
29
+ return Math.max(0, e);
30
+ }, D = async (e) => {
31
+ try {
32
+ let { data: t } = await S({ variables: { sessionId: e } });
33
+ return t?.revokeSession && await x(), t?.revokeSession ?? !1;
34
+ } catch (e) {
35
+ throw console.error("Failed to revoke session:", e), e;
36
+ }
37
+ }, O = async () => {
38
+ try {
39
+ let { data: e } = await C();
40
+ return e?.revokeAllSessions?.success && await x(), e?.revokeAllSessions ?? {
41
+ success: !1,
42
+ revokedCount: 0
43
+ };
44
+ } catch (e) {
45
+ throw console.error("Failed to revoke all sessions:", e), e;
46
+ }
47
+ }, k = y?.activeSessions?.totalCount ?? 0, A = y?.activeSessions?.hasMore ?? !1, j = Math.max(1, Math.ceil(k / f));
15
48
  return {
16
- validateTokens: async (e) => {
17
- try {
18
- let { data: t } = await d({ variables: { input: e } });
19
- return t.validateTokens;
20
- } catch (e) {
21
- throw console.error("Failed to validate tokens:", e), e;
22
- }
49
+ validateTokens: w,
50
+ isTokenExpired: T,
51
+ getTokenRemainingTime: E,
52
+ tokenInfo: g?.tokenInfo,
53
+ revokeSession: D,
54
+ revokeAllSessions: O,
55
+ activeSessions: y?.activeSessions?.sessions ?? [],
56
+ sessionsLoading: b,
57
+ refetchSessions: x,
58
+ page: p,
59
+ pageSize: f,
60
+ totalCount: k,
61
+ totalPages: j,
62
+ hasMore: A,
63
+ goToNextPage: () => {
64
+ A && m((e) => e + 1);
23
65
  },
24
- isTokenExpired: () => f?.tokenInfo?.expiresAt ? Date.now() / 1e3 > f.tokenInfo.expiresAt : !0,
25
- getTokenRemainingTime: () => {
26
- if (!f?.tokenInfo?.expiresAt) return 0;
27
- let e = f.tokenInfo.expiresAt - Date.now() / 1e3;
28
- return Math.max(0, e);
66
+ goToPrevPage: () => {
67
+ p > 0 && m((e) => e - 1);
29
68
  },
30
- tokenInfo: f?.tokenInfo,
31
- revokeSession: async (e) => {
32
- try {
33
- let { data: t } = await v({ variables: { sessionId: e } });
34
- return t?.revokeSession && await _(), t?.revokeSession ?? !1;
35
- } catch (e) {
36
- throw console.error("Failed to revoke session:", e), e;
37
- }
38
- },
39
- revokeAllSessions: async () => {
40
- try {
41
- let { data: e } = await y();
42
- return e?.revokeAllSessions?.success && await _(), e?.revokeAllSessions ?? {
43
- success: !1,
44
- revokedCount: 0
45
- };
46
- } catch (e) {
47
- throw console.error("Failed to revoke all sessions:", e), e;
48
- }
49
- },
50
- activeSessions: h?.activeSessions?.sessions ?? [],
51
- sessionsLoading: g,
52
- refetchSessions: _,
53
- isLoading: p || g,
54
- refetch: m
69
+ isLoading: _ || b,
70
+ refetch: v
55
71
  };
56
72
  }
57
73
  //#endregion
58
- export { c as useSessionOperations };
74
+ export { l as useSessionOperations };
59
75
 
60
76
  //# sourceMappingURL=useSessionOperations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSessionOperations.js","names":[],"sources":["../../../src/auth/hooks/useSessionOperations.ts"],"sourcesContent":["import { useQuery, useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport {\n TokenInfoDocument,\n ValidateTokensDocument,\n ActiveSessionsDocument,\n RevokeSessionDocument,\n RevokeAllSessionsDocument,\n} from \"../../generated/global-operations\";\nimport type {\n ActiveSessionsQuery,\n RevokeSessionMutation,\n RevokeSessionMutationVariables,\n RevokeAllSessionsMutation,\n RevokeAllSessionsMutationVariables,\n} from \"../../generated/global-operations\";\nimport type { TokenValidationResponse, ValidateTokensInput } from \"../types\";\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES\n// =============================================================================\n\ninterface TokenInfoQueryResponse {\n tokenInfo: TokenValidationResponse;\n}\n\ninterface ValidateTokensMutationResponse {\n validateTokens: TokenValidationResponse;\n}\n\ninterface ValidateTokensMutationVariables {\n input: ValidateTokensInput;\n}\n\ninterface UseSessionOperationsOptions {\n disableTokenInfoQuery?: boolean;\n}\n\n/**\n * Hook for session/token management operations\n */\nexport function useSessionOperations(\n options: UseSessionOperationsOptions = {},\n) {\n const store = useAuthStore();\n const disableTokenInfoQuery = options.disableTokenInfoQuery ?? false;\n const [validateTokensMutation] = useMutation<\n ValidateTokensMutationResponse,\n ValidateTokensMutationVariables\n >(ValidateTokensDocument);\n\n const { data, loading, refetch } = useQuery<TokenInfoQueryResponse>(\n TokenInfoDocument,\n {\n skip: disableTokenInfoQuery || !store.isAuthenticated,\n fetchPolicy: \"cache-and-network\",\n pollInterval: 5 * 60 * 1000, // Poll every 5 minutes (was 1 min — too aggressive)\n },\n );\n\n // Active sessions query\n // Pass the access token explicitly in Apollo operation context so the query\n // works both via the shell's MultiGatewayProvider auth link and in standalone mode.\n const {\n data: sessionsData,\n loading: sessionsLoading,\n refetch: refetchSessions,\n } = useQuery<ActiveSessionsQuery>(ActiveSessionsDocument, {\n skip: !store.isAuthenticated,\n fetchPolicy: \"cache-and-network\",\n context: store.accessToken\n ? {\n headers: {\n authorization: `Bearer ${store.accessToken}`,\n },\n }\n : undefined,\n });\n\n // Session mutations\n const [revokeSessionMutation] = useMutation<\n RevokeSessionMutation,\n RevokeSessionMutationVariables\n >(RevokeSessionDocument);\n\n const [revokeAllSessionsMutation] = useMutation<\n RevokeAllSessionsMutation,\n RevokeAllSessionsMutationVariables\n >(RevokeAllSessionsDocument);\n\n /**\n * Validate tokens\n */\n const validateTokens = async (\n input: ValidateTokensInput,\n ): Promise<TokenValidationResponse> => {\n try {\n const { data } = await validateTokensMutation({ variables: { input } });\n return data!.validateTokens;\n } catch (error) {\n console.error(\"Failed to validate tokens:\", error);\n throw error;\n }\n };\n\n /**\n * Check if token is expired\n */\n const isTokenExpired = (): boolean => {\n if (!data?.tokenInfo?.expiresAt) return true;\n return Date.now() / 1000 > data.tokenInfo.expiresAt;\n };\n\n /**\n * Get remaining time until token expires (in seconds)\n */\n const getTokenRemainingTime = (): number => {\n if (!data?.tokenInfo?.expiresAt) return 0;\n const remaining = data.tokenInfo.expiresAt - Date.now() / 1000;\n return Math.max(0, remaining);\n };\n\n /**\n * Revoke a specific session by ID\n */\n const revokeSession = async (sessionId: string): Promise<boolean> => {\n try {\n const { data } = await revokeSessionMutation({\n variables: { sessionId },\n });\n if (data?.revokeSession) {\n // Refetch active sessions to reflect the change\n await refetchSessions();\n }\n return data?.revokeSession ?? false;\n } catch (error) {\n console.error(\"Failed to revoke session:\", error);\n throw error;\n }\n };\n\n /**\n * Revoke all sessions except the current one\n */\n const revokeAllSessions = async (): Promise<{\n success: boolean;\n revokedCount: number;\n }> => {\n try {\n const { data } = await revokeAllSessionsMutation();\n if (data?.revokeAllSessions?.success) {\n // Refetch active sessions to reflect the change\n await refetchSessions();\n }\n return data?.revokeAllSessions ?? { success: false, revokedCount: 0 };\n } catch (error) {\n console.error(\"Failed to revoke all sessions:\", error);\n throw error;\n }\n };\n\n return {\n // Token operations\n validateTokens,\n isTokenExpired,\n getTokenRemainingTime,\n tokenInfo: data?.tokenInfo,\n // Session operations\n revokeSession,\n revokeAllSessions,\n activeSessions: sessionsData?.activeSessions?.sessions ?? [],\n sessionsLoading,\n refetchSessions,\n // General state\n isLoading: loading || sessionsLoading,\n refetch,\n };\n}\n"],"mappings":";;;;AAyCA,SAAgB,EACd,IAAuC,EAAE,EACzC;CACA,IAAM,IAAQ,GAAc,EACtB,IAAwB,EAAQ,yBAAyB,IACzD,CAAC,KAA0B,EAG/B,EAAuB,EAEnB,EAAE,SAAM,YAAS,eAAY,EACjC,GACA;EACE,MAAM,KAAyB,CAAC,EAAM;EACtC,aAAa;EACb,cAAc,MAAS;EACxB,CACF,EAKK,EACJ,MAAM,GACN,SAAS,GACT,SAAS,MACP,EAA8B,GAAwB;EACxD,MAAM,CAAC,EAAM;EACb,aAAa;EACb,SAAS,EAAM,cACX,EACE,SAAS,EACP,eAAe,UAAU,EAAM,eAChC,EACF,GACD,KAAA;EACL,CAAC,EAGI,CAAC,KAAyB,EAG9B,EAAsB,EAElB,CAAC,KAA6B,EAGlC,EAA0B;AAyE5B,QAAO;EAEL,gBAtEqB,OACrB,MACqC;AACrC,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAuB,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC;AACvE,WAAO,EAAM;YACN,GAAO;AAEd,UADA,QAAQ,MAAM,8BAA8B,EAAM,EAC5C;;;EA+DR,sBAvDK,GAAM,WAAW,YACf,KAAK,KAAK,GAAG,MAAO,EAAK,UAAU,YADF;EAwDxC,6BAjD0C;AAC1C,OAAI,CAAC,GAAM,WAAW,UAAW,QAAO;GACxC,IAAM,IAAY,EAAK,UAAU,YAAY,KAAK,KAAK,GAAG;AAC1D,UAAO,KAAK,IAAI,GAAG,EAAU;;EA+C7B,WAAW,GAAM;EAEjB,eA3CoB,OAAO,MAAwC;AACnE,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW,EAAE,cAAW,EACzB,CAAC;AAKF,WAJI,GAAM,iBAER,MAAM,GAAiB,EAElB,GAAM,iBAAiB;YACvB,GAAO;AAEd,UADA,QAAQ,MAAM,6BAA6B,EAAM,EAC3C;;;EAgCR,mBAzBwB,YAGpB;AACJ,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,GAA2B;AAKlD,WAJI,GAAM,mBAAmB,WAE3B,MAAM,GAAiB,EAElB,GAAM,qBAAqB;KAAE,SAAS;KAAO,cAAc;KAAG;YAC9D,GAAO;AAEd,UADA,QAAQ,MAAM,kCAAkC,EAAM,EAChD;;;EAaR,gBAAgB,GAAc,gBAAgB,YAAY,EAAE;EAC5D;EACA;EAEA,WAAW,KAAW;EACtB;EACD"}
1
+ {"version":3,"file":"useSessionOperations.js","names":[],"sources":["../../../src/auth/hooks/useSessionOperations.ts"],"sourcesContent":["import { useState } from \"react\";\nimport { useQuery, useMutation } from \"@apollo/client/react\";\nimport { useAuthStore } from \"../store/authStore\";\nimport {\n TokenInfoDocument,\n ValidateTokensDocument,\n ActiveSessionsDocument,\n RevokeSessionDocument,\n RevokeAllSessionsDocument,\n} from \"../../generated/global-operations\";\nimport type {\n ActiveSessionsQuery,\n ActiveSessionsQueryVariables,\n RevokeSessionMutation,\n RevokeSessionMutationVariables,\n RevokeAllSessionsMutation,\n RevokeAllSessionsMutationVariables,\n} from \"../../generated/global-operations\";\nimport type { TokenValidationResponse, ValidateTokensInput } from \"../types\";\n\n// =============================================================================\n// GRAPHQL RESPONSE INTERFACES\n// =============================================================================\n\ninterface TokenInfoQueryResponse {\n tokenInfo: TokenValidationResponse;\n}\n\ninterface ValidateTokensMutationResponse {\n validateTokens: TokenValidationResponse;\n}\n\ninterface ValidateTokensMutationVariables {\n input: ValidateTokensInput;\n}\n\ninterface UseSessionOperationsOptions {\n disableTokenInfoQuery?: boolean;\n /** Number of sessions to load per page (default: 10) */\n pageSize?: number;\n}\n\n/**\n * Hook for session/token management operations\n */\nexport function useSessionOperations(\n options: UseSessionOperationsOptions = {},\n) {\n const store = useAuthStore();\n const disableTokenInfoQuery = options.disableTokenInfoQuery ?? false;\n const pageSize = options.pageSize ?? 10;\n\n // Pagination state: page is 0-based for offset calculation\n const [page, setPage] = useState(0);\n\n const [validateTokensMutation] = useMutation<\n ValidateTokensMutationResponse,\n ValidateTokensMutationVariables\n >(ValidateTokensDocument);\n\n const { data, loading, refetch } = useQuery<TokenInfoQueryResponse>(\n TokenInfoDocument,\n {\n skip: disableTokenInfoQuery || !store.isAuthenticated,\n fetchPolicy: \"cache-and-network\",\n pollInterval: 5 * 60 * 1000, // Poll every 5 minutes (was 1 min — too aggressive)\n },\n );\n\n // Active sessions query\n // Pass the access token explicitly in Apollo operation context so the query\n // works both via the shell's MultiGatewayProvider auth link and in standalone mode.\n const {\n data: sessionsData,\n loading: sessionsLoading,\n refetch: refetchSessions,\n } = useQuery<ActiveSessionsQuery, ActiveSessionsQueryVariables>(\n ActiveSessionsDocument,\n {\n skip: !store.isAuthenticated,\n fetchPolicy: \"cache-and-network\",\n variables: {\n pagination: {\n limit: pageSize,\n offset: page * pageSize,\n },\n },\n context: store.accessToken\n ? {\n headers: {\n authorization: `Bearer ${store.accessToken}`,\n },\n }\n : undefined,\n },\n );\n\n // Session mutations\n const [revokeSessionMutation] = useMutation<\n RevokeSessionMutation,\n RevokeSessionMutationVariables\n >(RevokeSessionDocument);\n\n const [revokeAllSessionsMutation] = useMutation<\n RevokeAllSessionsMutation,\n RevokeAllSessionsMutationVariables\n >(RevokeAllSessionsDocument);\n\n /**\n * Validate tokens\n */\n const validateTokens = async (\n input: ValidateTokensInput,\n ): Promise<TokenValidationResponse> => {\n try {\n const { data } = await validateTokensMutation({ variables: { input } });\n return data!.validateTokens;\n } catch (error) {\n console.error(\"Failed to validate tokens:\", error);\n throw error;\n }\n };\n\n /**\n * Check if token is expired\n */\n const isTokenExpired = (): boolean => {\n if (!data?.tokenInfo?.expiresAt) return true;\n return Date.now() / 1000 > data.tokenInfo.expiresAt;\n };\n\n /**\n * Get remaining time until token expires (in seconds)\n */\n const getTokenRemainingTime = (): number => {\n if (!data?.tokenInfo?.expiresAt) return 0;\n const remaining = data.tokenInfo.expiresAt - Date.now() / 1000;\n return Math.max(0, remaining);\n };\n\n /**\n * Revoke a specific session by ID\n */\n const revokeSession = async (sessionId: string): Promise<boolean> => {\n try {\n const { data } = await revokeSessionMutation({\n variables: { sessionId },\n });\n if (data?.revokeSession) {\n // Refetch active sessions to reflect the change\n await refetchSessions();\n }\n return data?.revokeSession ?? false;\n } catch (error) {\n console.error(\"Failed to revoke session:\", error);\n throw error;\n }\n };\n\n /**\n * Revoke all sessions except the current one\n */\n const revokeAllSessions = async (): Promise<{\n success: boolean;\n revokedCount: number;\n }> => {\n try {\n const { data } = await revokeAllSessionsMutation();\n if (data?.revokeAllSessions?.success) {\n // Refetch active sessions to reflect the change\n await refetchSessions();\n }\n return data?.revokeAllSessions ?? { success: false, revokedCount: 0 };\n } catch (error) {\n console.error(\"Failed to revoke all sessions:\", error);\n throw error;\n }\n };\n\n const totalCount = sessionsData?.activeSessions?.totalCount ?? 0;\n const hasMore = sessionsData?.activeSessions?.hasMore ?? false;\n const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));\n\n const goToNextPage = () => {\n if (hasMore) {\n setPage((prev) => prev + 1);\n }\n };\n\n const goToPrevPage = () => {\n if (page > 0) {\n setPage((prev) => prev - 1);\n }\n };\n\n return {\n // Token operations\n validateTokens,\n isTokenExpired,\n getTokenRemainingTime,\n tokenInfo: data?.tokenInfo,\n // Session operations\n revokeSession,\n revokeAllSessions,\n activeSessions: sessionsData?.activeSessions?.sessions ?? [],\n sessionsLoading,\n refetchSessions,\n // Pagination\n page,\n pageSize,\n totalCount,\n totalPages,\n hasMore,\n goToNextPage,\n goToPrevPage,\n // General state\n isLoading: loading || sessionsLoading,\n refetch,\n };\n}\n"],"mappings":";;;;;AA6CA,SAAgB,EACd,IAAuC,EAAE,EACzC;CACA,IAAM,IAAQ,GAAc,EACtB,IAAwB,EAAQ,yBAAyB,IACzD,IAAW,EAAQ,YAAY,IAG/B,CAAC,GAAM,KAAW,EAAS,EAAE,EAE7B,CAAC,KAA0B,EAG/B,EAAuB,EAEnB,EAAE,SAAM,YAAS,eAAY,EACjC,GACA;EACE,MAAM,KAAyB,CAAC,EAAM;EACtC,aAAa;EACb,cAAc,MAAS;EACxB,CACF,EAKK,EACJ,MAAM,GACN,SAAS,GACT,SAAS,MACP,EACF,GACA;EACE,MAAM,CAAC,EAAM;EACb,aAAa;EACb,WAAW,EACT,YAAY;GACV,OAAO;GACP,QAAQ,IAAO;GAChB,EACF;EACD,SAAS,EAAM,cACX,EACE,SAAS,EACP,eAAe,UAAU,EAAM,eAChC,EACF,GACD,KAAA;EACL,CACF,EAGK,CAAC,KAAyB,EAG9B,EAAsB,EAElB,CAAC,KAA6B,EAGlC,EAA0B,EAKtB,IAAiB,OACrB,MACqC;AACrC,MAAI;GACF,IAAM,EAAE,YAAS,MAAM,EAAuB,EAAE,WAAW,EAAE,UAAO,EAAE,CAAC;AACvE,UAAO,EAAM;WACN,GAAO;AAEd,SADA,QAAQ,MAAM,8BAA8B,EAAM,EAC5C;;IAOJ,UACC,GAAM,WAAW,YACf,KAAK,KAAK,GAAG,MAAO,EAAK,UAAU,YADF,IAOpC,UAAsC;AAC1C,MAAI,CAAC,GAAM,WAAW,UAAW,QAAO;EACxC,IAAM,IAAY,EAAK,UAAU,YAAY,KAAK,KAAK,GAAG;AAC1D,SAAO,KAAK,IAAI,GAAG,EAAU;IAMzB,IAAgB,OAAO,MAAwC;AACnE,MAAI;GACF,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW,EAAE,cAAW,EACzB,CAAC;AAKF,UAJI,GAAM,iBAER,MAAM,GAAiB,EAElB,GAAM,iBAAiB;WACvB,GAAO;AAEd,SADA,QAAQ,MAAM,6BAA6B,EAAM,EAC3C;;IAOJ,IAAoB,YAGpB;AACJ,MAAI;GACF,IAAM,EAAE,YAAS,MAAM,GAA2B;AAKlD,UAJI,GAAM,mBAAmB,WAE3B,MAAM,GAAiB,EAElB,GAAM,qBAAqB;IAAE,SAAS;IAAO,cAAc;IAAG;WAC9D,GAAO;AAEd,SADA,QAAQ,MAAM,kCAAkC,EAAM,EAChD;;IAIJ,IAAa,GAAc,gBAAgB,cAAc,GACzD,IAAU,GAAc,gBAAgB,WAAW,IACnD,IAAa,KAAK,IAAI,GAAG,KAAK,KAAK,IAAa,EAAS,CAAC;AAchE,QAAO;EAEL;EACA;EACA;EACA,WAAW,GAAM;EAEjB;EACA;EACA,gBAAgB,GAAc,gBAAgB,YAAY,EAAE;EAC5D;EACA;EAEA;EACA;EACA;EACA;EACA;EACA,oBA9ByB;AACzB,GAAI,KACF,GAAS,MAAS,IAAO,EAAE;;EA6B7B,oBAzByB;AACzB,GAAI,IAAO,KACT,GAAS,MAAS,IAAO,EAAE;;EAyB7B,WAAW,KAAW;EACtB;EACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordResetPage.d.ts","sourceRoot":"","sources":["../../../src/auth/pages/PasswordResetPage.tsx"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,wBAAgB,iBAAiB,4CAmhBhC"}
1
+ {"version":3,"file":"PasswordResetPage.d.ts","sourceRoot":"","sources":["../../../src/auth/pages/PasswordResetPage.tsx"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,wBAAgB,iBAAiB,4CAmiBhC"}
@@ -141,7 +141,8 @@ function y() {
141
141
  try {
142
142
  await E(C, j), B(!0);
143
143
  } catch (e) {
144
- H(x("auth.passwordReset.resetFailed", "Failed to reset password. The link may have expired.")), console.error("Failed to reset password:", e);
144
+ let t = e instanceof Error ? e.message : "";
145
+ H(t === "TOKEN_EXPIRED" ? x("auth.passwordReset.errorTokenExpired", "This password reset link has expired. Please request a new one.") : t === "INVALID_TOKEN" ? x("auth.passwordReset.errorInvalidToken", "This password reset link is invalid or has already been used.") : x("auth.passwordReset.errorGeneric", "Failed to reset password. Please try again."));
145
146
  }
146
147
  }
147
148
  },
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordResetPage.js","names":[],"sources":["../../../src/auth/pages/PasswordResetPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useSearchParams, useNavigate } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { usePasswordOperations } from \"../hooks\";\nimport { useCaptchaV3 } from \"../hooks/useCaptchaV3\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n EmailIcon,\n LockIcon,\n EyeIcon,\n EyeOffIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n} from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Password reset page with professional design\n * Handles both requesting reset and completing reset with token\n */\nexport function PasswordResetPage() {\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n // Preserve returnTo so after reset→login the user ends up on the right product domain\n const returnTo = searchParams.get(\"returnTo\");\n const { requestPasswordReset, resetPassword, isLoading } =\n usePasswordOperations();\n const { executeRecaptcha: executeCaptchaV3 } = useCaptchaV3();\n const [email, setEmail] = useState(\"\");\n const [newPassword, setNewPassword] = useState(\"\");\n const [confirmPassword, setConfirmPassword] = useState(\"\");\n const [showPassword, setShowPassword] = useState(false);\n const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n const [success, setSuccess] = useState(false);\n const [error, setError] = useState(\"\");\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Auto-redirect countdown after successful password reset (only for token flow)\n useEffect(() => {\n if (!success || !token) return;\n\n const loginPath = returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\";\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigate(loginPath);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [success, token, navigate, returnTo]);\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = newPassword;\n return [\n {\n label: tr(\"auth.passwordReset.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.passwordReset.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthFair\", \"fair\"),\n color: \"bg-orange-500\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.passwordReset.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-bg-subtle0\",\n width: \"w-full\",\n };\n })();\n\n const handleRequestReset = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n try {\n // Get reCAPTCHA v3 token (no-op when captcha is disabled)\n const recaptchaToken = await executeCaptchaV3(\"password_reset\");\n await requestPasswordReset(email, recaptchaToken);\n setSuccess(true);\n } catch (err) {\n setError(\n tr(\n \"auth.passwordReset.sendFailed\",\n \"Failed to send reset email. Please try again.\",\n ),\n );\n console.error(\"Failed to request password reset:\", err);\n }\n };\n\n const handleResetPassword = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n\n if (!token) return;\n\n if (newPassword !== confirmPassword) {\n setError(\n tr(\"auth.passwordReset.passwordsNoMatch\", \"Passwords do not match\"),\n );\n return;\n }\n\n try {\n await resetPassword(token, newPassword);\n setSuccess(true);\n } catch (err) {\n setError(\n tr(\n \"auth.passwordReset.resetFailed\",\n \"Failed to reset password. The link may have expired.\",\n ),\n );\n console.error(\"Failed to reset password:\", err);\n }\n };\n\n if (success) {\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.passwordUpdated\", \"Password updated\")\n : tr(\"auth.passwordReset.checkEmail\", \"Check your email\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.successSubtitle\",\n \"Your password has been successfully reset\",\n )\n : tr(\n \"auth.passwordReset.sentInstructions\",\n \"We sent you instructions to reset your password\",\n )\n }\n >\n {/* Success Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"w-16 h-16 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"w-8 h-8 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {token\n ? tr(\n \"auth.passwordReset.canNowSignIn\",\n \"You can now use your new password to sign in to your account.\",\n )\n : tr(\n \"auth.passwordReset.emailSentTo\",\n \"We sent an email to {{email}}. Click the link in the email to reset your password.\",\n ).replace(\"{{email}}\", email)}\n </p>\n\n {token ? (\n <div className=\"space-y-4\">\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.passwordReset.redirectingIn\",\n \"Redirecting to sign in in\",\n )}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.passwordReset.seconds\", \"seconds...\")}\n </p>\n <button\n onClick={() =>\n navigate(\n returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\",\n )\n }\n className=\"auth-primary-button w-full px-4 py-3 font-semibold\"\n >\n {tr(\"auth.passwordReset.signInNow\", \"Sign in now\")}\n </button>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <button\n onClick={() => setSuccess(false)}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all\"\n >\n {tr(\n \"auth.passwordReset.didntReceive\",\n \"Didn't receive the email? Try again\",\n )}\n </button>\n <button\n onClick={() => navigate(\"/auth/login\")}\n className=\"w-full py-3 px-4 rounded-xl text-[var(--color-text-secondary)] font-medium hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.passwordReset.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n )}\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.createNewPassword\", \"Create new password\")\n : tr(\"auth.passwordReset.resetTitle\", \"Reset your password\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.enterNewPassword\",\n \"Enter a new password for your account\",\n )\n : tr(\n \"auth.passwordReset.enterEmail\",\n \"Enter your email and we'll send you a reset link\",\n )\n }\n >\n {token ? (\n <form onSubmit={handleResetPassword} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* New Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"newPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.newPasswordLabel\", \"New Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"newPassword\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n autoComplete=\"new-password\"\n value={newPassword}\n onChange={(e) => setNewPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.newPasswordPlaceholder\",\n \"Enter new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"w-5 h-5\" />\n ) : (\n <EyeIcon className=\"w-5 h-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {newPassword && (\n <div className=\"space-y-3 pt-2\">\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\n \"auth.passwordReset.passwordStrength\",\n \"Password strength:\",\n )}{\" \"}\n <span className=\"font-medium\">\n {passwordStrength.level}\n </span>\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"w-4 h-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"w-4 h-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-[var(--color-status-success-text)]\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Confirm Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"confirmPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\n \"auth.passwordReset.confirmPasswordLabel\",\n \"Confirm Password\",\n )}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"confirmPassword\"\n type={showConfirmPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n autoComplete=\"new-password\"\n aria-invalid={\n confirmPassword.length > 0 && newPassword !== confirmPassword\n }\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.confirmPasswordPlaceholder\",\n \"Confirm new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowConfirmPassword(!showConfirmPassword)}\n aria-label={\n showConfirmPassword ? \"Hide password\" : \"Show password\"\n }\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showConfirmPassword ? (\n <EyeOffIcon className=\"w-5 h-5\" />\n ) : (\n <EyeIcon className=\"w-5 h-5\" />\n )}\n </button>\n </div>\n {confirmPassword && newPassword !== confirmPassword && (\n <p className=\"text-xs text-[var(--color-status-error-text)]\">\n {tr(\n \"auth.passwordReset.passwordsMismatch\",\n \"Passwords do not match\",\n )}\n </p>\n )}\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={\n isLoading ||\n (confirmPassword.length > 0 && newPassword !== confirmPassword)\n }\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>\n {tr(\"auth.passwordReset.resetting\", \"Resetting password...\")}\n </span>\n </>\n ) : (\n tr(\"auth.passwordReset.resetButton\", \"Reset password\")\n )}\n </button>\n </form>\n ) : (\n <form onSubmit={handleRequestReset} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n autoComplete=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>{tr(\"auth.passwordReset.sending\", \"Sending...\")}</span>\n </>\n ) : (\n tr(\"auth.passwordReset.sendResetLink\", \"Send reset link\")\n )}\n </button>\n </form>\n )}\n\n {/* Back to Sign In */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.passwordReset.rememberPassword\", \"Remember your password?\")}{\" \"}\n <button\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.passwordReset.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PasswordResetPage.js","names":[],"sources":["../../../src/auth/pages/PasswordResetPage.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { useSearchParams, useNavigate } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell\";\nimport { usePasswordOperations } from \"../hooks\";\nimport { useCaptchaV3 } from \"../hooks/useCaptchaV3\";\nimport { AuthLayout } from \"../components/AuthLayout\";\nimport {\n EmailIcon,\n LockIcon,\n EyeIcon,\n EyeOffIcon,\n CheckIcon,\n XIcon,\n SpinnerIcon,\n} from \"../components/AuthIcons\";\n\nconst REDIRECT_DELAY = 5; // seconds\n\n/**\n * Password reset page with professional design\n * Handles both requesting reset and completing reset with token\n */\nexport function PasswordResetPage() {\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [searchParams] = useSearchParams();\n const token = searchParams.get(\"token\");\n // Preserve returnTo so after reset→login the user ends up on the right product domain\n const returnTo = searchParams.get(\"returnTo\");\n const { requestPasswordReset, resetPassword, isLoading } =\n usePasswordOperations();\n const { executeRecaptcha: executeCaptchaV3 } = useCaptchaV3();\n const [email, setEmail] = useState(\"\");\n const [newPassword, setNewPassword] = useState(\"\");\n const [confirmPassword, setConfirmPassword] = useState(\"\");\n const [showPassword, setShowPassword] = useState(false);\n const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n const [success, setSuccess] = useState(false);\n const [error, setError] = useState(\"\");\n const [countdown, setCountdown] = useState(REDIRECT_DELAY);\n\n // Auto-redirect countdown after successful password reset (only for token flow)\n useEffect(() => {\n if (!success || !token) return;\n\n const loginPath = returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\";\n const timer = setInterval(() => {\n setCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n navigate(loginPath);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [success, token, navigate, returnTo]);\n\n // Password strength indicators\n const passwordChecks = (() => {\n const password = newPassword;\n return [\n {\n label: tr(\"auth.passwordReset.pwMin8\", \"At least 12 characters\"),\n met: password.length >= 12,\n },\n {\n label: tr(\"auth.passwordReset.pwUppercase\", \"One uppercase letter\"),\n met: /[A-Z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwLowercase\", \"One lowercase letter\"),\n met: /[a-z]/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwNumber\", \"One number\"),\n met: /\\d/.test(password),\n },\n {\n label: tr(\"auth.passwordReset.pwSpecial\", \"One special character\"),\n met: /[!@#$%^&*(),.?\":{}|<>]/.test(password),\n },\n ];\n })();\n\n const passwordStrength = (() => {\n const metCount = passwordChecks.filter((c) => c.met).length;\n if (metCount <= 2)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthWeak\", \"weak\"),\n color: \"bg-status-error-bg-subtle0\",\n width: \"w-1/4\",\n };\n if (metCount <= 3)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthFair\", \"fair\"),\n color: \"bg-orange-500\",\n width: \"w-2/4\",\n };\n if (metCount <= 4)\n return {\n level: tr(\"auth.passwordReset.passwordStrengthGood\", \"good\"),\n color: \"bg-status-warning-bg-subtle0\",\n width: \"w-3/4\",\n };\n return {\n level: tr(\"auth.passwordReset.passwordStrengthStrong\", \"strong\"),\n color: \"bg-status-success-bg-subtle0\",\n width: \"w-full\",\n };\n })();\n\n const handleRequestReset = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n try {\n // Get reCAPTCHA v3 token (no-op when captcha is disabled)\n const recaptchaToken = await executeCaptchaV3(\"password_reset\");\n await requestPasswordReset(email, recaptchaToken);\n setSuccess(true);\n } catch (err) {\n setError(\n tr(\n \"auth.passwordReset.sendFailed\",\n \"Failed to send reset email. Please try again.\",\n ),\n );\n console.error(\"Failed to request password reset:\", err);\n }\n };\n\n const handleResetPassword = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(\"\");\n\n if (!token) return;\n\n if (newPassword !== confirmPassword) {\n setError(\n tr(\"auth.passwordReset.passwordsNoMatch\", \"Passwords do not match\"),\n );\n return;\n }\n\n try {\n await resetPassword(token, newPassword);\n setSuccess(true);\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"\";\n if (msg === \"TOKEN_EXPIRED\") {\n setError(\n tr(\n \"auth.passwordReset.errorTokenExpired\",\n \"This password reset link has expired. Please request a new one.\",\n ),\n );\n } else if (msg === \"INVALID_TOKEN\") {\n setError(\n tr(\n \"auth.passwordReset.errorInvalidToken\",\n \"This password reset link is invalid or has already been used.\",\n ),\n );\n } else {\n setError(\n tr(\n \"auth.passwordReset.errorGeneric\",\n \"Failed to reset password. Please try again.\",\n ),\n );\n }\n }\n };\n\n if (success) {\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.passwordUpdated\", \"Password updated\")\n : tr(\"auth.passwordReset.checkEmail\", \"Check your email\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.successSubtitle\",\n \"Your password has been successfully reset\",\n )\n : tr(\n \"auth.passwordReset.sentInstructions\",\n \"We sent you instructions to reset your password\",\n )\n }\n >\n {/* Success Icon */}\n <div className=\"flex justify-center mb-6\">\n <div className=\"w-16 h-16 rounded-full bg-[var(--color-status-success-bg-subtle)] flex items-center justify-center\">\n <svg\n className=\"w-8 h-8 text-[var(--color-status-success-text)]\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n </div>\n\n <p className=\"text-center text-[var(--color-text-secondary)] mb-8\">\n {token\n ? tr(\n \"auth.passwordReset.canNowSignIn\",\n \"You can now use your new password to sign in to your account.\",\n )\n : tr(\n \"auth.passwordReset.emailSentTo\",\n \"We sent an email to {{email}}. Click the link in the email to reset your password.\",\n ).replace(\"{{email}}\", email)}\n </p>\n\n {token ? (\n <div className=\"space-y-4\">\n <p className=\"text-center text-sm text-[var(--color-text-tertiary)]\">\n {tr(\n \"auth.passwordReset.redirectingIn\",\n \"Redirecting to sign in in\",\n )}{\" \"}\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {countdown}\n </span>{\" \"}\n {tr(\"auth.passwordReset.seconds\", \"seconds...\")}\n </p>\n <button\n onClick={() =>\n navigate(\n returnTo\n ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}`\n : \"/auth/login\",\n )\n }\n className=\"auth-primary-button w-full px-4 py-3 font-semibold\"\n >\n {tr(\"auth.passwordReset.signInNow\", \"Sign in now\")}\n </button>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <button\n onClick={() => setSuccess(false)}\n className=\"w-full py-3 px-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] font-medium hover:bg-[var(--color-bg-sunken)] transition-all\"\n >\n {tr(\n \"auth.passwordReset.didntReceive\",\n \"Didn't receive the email? Try again\",\n )}\n </button>\n <button\n onClick={() => navigate(\"/auth/login\")}\n className=\"w-full py-3 px-4 rounded-xl text-[var(--color-text-secondary)] font-medium hover:text-[var(--color-text-primary)] transition-colors\"\n >\n {tr(\"auth.passwordReset.backToSignIn\", \"Back to sign in\")}\n </button>\n </div>\n )}\n </AuthLayout>\n );\n }\n\n return (\n <AuthLayout\n title={\n token\n ? tr(\"auth.passwordReset.createNewPassword\", \"Create new password\")\n : tr(\"auth.passwordReset.resetTitle\", \"Reset your password\")\n }\n subtitle={\n token\n ? tr(\n \"auth.passwordReset.enterNewPassword\",\n \"Enter a new password for your account\",\n )\n : tr(\n \"auth.passwordReset.enterEmail\",\n \"Enter your email and we'll send you a reset link\",\n )\n }\n >\n {token ? (\n <form onSubmit={handleResetPassword} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* New Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"newPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.newPasswordLabel\", \"New Password\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"newPassword\"\n type={showPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n autoComplete=\"new-password\"\n value={newPassword}\n onChange={(e) => setNewPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.newPasswordPlaceholder\",\n \"Enter new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowPassword(!showPassword)}\n aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showPassword ? (\n <EyeOffIcon className=\"w-5 h-5\" />\n ) : (\n <EyeIcon className=\"w-5 h-5\" />\n )}\n </button>\n </div>\n\n {/* Password Strength Indicator */}\n {newPassword && (\n <div className=\"space-y-3 pt-2\">\n <div className=\"space-y-1\">\n <div className=\"h-1.5 bg-[var(--color-bg-sunken)] rounded-full overflow-hidden\">\n <div\n className={`h-full ${passwordStrength.color} ${passwordStrength.width} transition-all duration-300`}\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-tertiary)] capitalize\">\n {tr(\n \"auth.passwordReset.passwordStrength\",\n \"Password strength:\",\n )}{\" \"}\n <span className=\"font-medium\">\n {passwordStrength.level}\n </span>\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-2\">\n {passwordChecks.map((check, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n {check.met ? (\n <CheckIcon className=\"w-4 h-4 text-status-success-text\" />\n ) : (\n <XIcon className=\"w-4 h-4 text-[var(--color-text-tertiary)]\" />\n )}\n <span\n className={`text-xs ${\n check.met\n ? \"text-[var(--color-status-success-text)]\"\n : \"text-[var(--color-text-tertiary)]\"\n }`}\n >\n {check.label}\n </span>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Confirm Password Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"confirmPassword\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\n \"auth.passwordReset.confirmPasswordLabel\",\n \"Confirm Password\",\n )}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <LockIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"confirmPassword\"\n type={showConfirmPassword ? \"text\" : \"password\"}\n required\n aria-required=\"true\"\n autoComplete=\"new-password\"\n aria-invalid={\n confirmPassword.length > 0 && newPassword !== confirmPassword\n }\n value={confirmPassword}\n onChange={(e) => setConfirmPassword(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-12 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.confirmPasswordPlaceholder\",\n \"Confirm new password\",\n )}\n />\n <button\n type=\"button\"\n onClick={() => setShowConfirmPassword(!showConfirmPassword)}\n aria-label={\n showConfirmPassword ? \"Hide password\" : \"Show password\"\n }\n className=\"absolute inset-y-0 right-0 pr-3.5 flex items-center text-[var(--color-text-tertiary)] hover:text-[var(--color-text-secondary)] transition-colors\"\n >\n {showConfirmPassword ? (\n <EyeOffIcon className=\"w-5 h-5\" />\n ) : (\n <EyeIcon className=\"w-5 h-5\" />\n )}\n </button>\n </div>\n {confirmPassword && newPassword !== confirmPassword && (\n <p className=\"text-xs text-[var(--color-status-error-text)]\">\n {tr(\n \"auth.passwordReset.passwordsMismatch\",\n \"Passwords do not match\",\n )}\n </p>\n )}\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={\n isLoading ||\n (confirmPassword.length > 0 && newPassword !== confirmPassword)\n }\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>\n {tr(\"auth.passwordReset.resetting\", \"Resetting password...\")}\n </span>\n </>\n ) : (\n tr(\"auth.passwordReset.resetButton\", \"Reset password\")\n )}\n </button>\n </form>\n ) : (\n <form onSubmit={handleRequestReset} className=\"space-y-5\">\n {/* Error Alert */}\n {error && (\n <div className=\"flex items-start gap-3 p-4 rounded-xl bg-[var(--color-status-error-bg-subtle)] border border-[var(--color-status-error-border)]\">\n <svg\n className=\"w-5 h-5 text-[var(--color-status-error-text)] flex-shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error}\n </p>\n </div>\n )}\n\n {/* Email Input */}\n <div className=\"space-y-2\">\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr(\"auth.passwordReset.emailLabel\", \"Email address\")}\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none\">\n <EmailIcon className=\"w-5 h-5 text-[var(--color-text-tertiary)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n required\n aria-required=\"true\"\n autoComplete=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className=\"block w-full pl-[3.5rem] pr-4 py-3 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:ring-2 focus:ring-action-primary-bg/20 focus:border-action-primary-bg transition-all\"\n placeholder={tr(\n \"auth.passwordReset.emailPlaceholder\",\n \"you@example.com\",\n )}\n />\n </div>\n </div>\n\n {/* Submit Button */}\n <button\n type=\"submit\"\n disabled={isLoading}\n className=\"auth-primary-button flex w-full items-center justify-center gap-2 px-4 py-3 font-semibold\"\n >\n {isLoading ? (\n <>\n <SpinnerIcon className=\"w-5 h-5 animate-spin\" />\n <span>{tr(\"auth.passwordReset.sending\", \"Sending...\")}</span>\n </>\n ) : (\n tr(\"auth.passwordReset.sendResetLink\", \"Send reset link\")\n )}\n </button>\n </form>\n )}\n\n {/* Back to Sign In */}\n <p className=\"mt-8 text-center text-sm text-[var(--color-text-secondary)]\">\n {tr(\"auth.passwordReset.rememberPassword\", \"Remember your password?\")}{\" \"}\n <button\n onClick={() => navigate(\"/auth/login\")}\n className=\"font-semibold text-text-link hover:text-text-linkHover transition-colors\"\n >\n {tr(\"auth.passwordReset.signIn\", \"Sign in\")}\n </button>\n </p>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,11 +1,8 @@
1
1
  /**
2
2
  * Active sessions management page with professional UI
3
3
  *
4
- * Fragment: SessionCard_Session (graphql/operations/global/fragments/SessionCard.fragment.graphql)
5
- * Query: ActiveSessions (graphql/operations/global/queries/sessions.graphql)
6
- *
7
- * TODO: When activeSessions backend migrates to SessionConnection type,
8
- * update query to use: activeSessions { sessions: [...], totalCount, hasMore }
4
+ * Fragment: SessionCard_Session (src/operations/global/fragments/SessionCard.fragment.graphql)
5
+ * Query: ActiveSessions (src/operations/global/queries/sessions.graphql)
9
6
  */
10
7
  export declare function SessionsPage(): import("react/jsx-runtime").JSX.Element;
11
8
  //# sourceMappingURL=SessionsPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionsPage.d.ts","sourceRoot":"","sources":["../../../src/auth/pages/SessionsPage.tsx"],"names":[],"mappings":"AAaA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,4CAwb3B"}
1
+ {"version":3,"file":"SessionsPage.d.ts","sourceRoot":"","sources":["../../../src/auth/pages/SessionsPage.tsx"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,wBAAgB,YAAY,4CA2b3B"}