@shipfox/client-invitations 3.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/complete-acceptance.d.ts +1 -1
  4. package/dist/complete-acceptance.js +1 -1
  5. package/dist/complete-acceptance.js.map +1 -1
  6. package/dist/core/invitation-preview.d.ts +21 -0
  7. package/dist/core/invitation-preview.d.ts.map +1 -0
  8. package/dist/core/invitation-preview.js +5 -0
  9. package/dist/core/invitation-preview.js.map +1 -0
  10. package/dist/hooks/api/invitation-preview-mapper.d.ts +4 -0
  11. package/dist/hooks/api/invitation-preview-mapper.d.ts.map +1 -0
  12. package/dist/hooks/api/invitation-preview-mapper.js +32 -0
  13. package/dist/hooks/api/invitation-preview-mapper.js.map +1 -0
  14. package/dist/hooks/api/preview-invitation.d.ts +12 -7
  15. package/dist/hooks/api/preview-invitation.d.ts.map +1 -1
  16. package/dist/hooks/api/preview-invitation.js +12 -6
  17. package/dist/hooks/api/preview-invitation.js.map +1 -1
  18. package/dist/index.d.ts +2 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/pages/invitation-accept-page.d.ts.map +1 -1
  23. package/dist/pages/invitation-accept-page.js +18 -23
  24. package/dist/pages/invitation-accept-page.js.map +1 -1
  25. package/dist/tsconfig.test.tsbuildinfo +1 -1
  26. package/package.json +5 -24
  27. package/src/complete-acceptance.ts +1 -1
  28. package/src/core/invitation-preview.ts +18 -0
  29. package/src/hooks/api/invitation-preview-mapper.test.ts +36 -0
  30. package/src/hooks/api/invitation-preview-mapper.ts +22 -0
  31. package/src/hooks/api/preview-invitation.ts +24 -9
  32. package/src/index.ts +2 -0
  33. package/src/pages/invitation-accept-page.tsx +18 -24
  34. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 7 files with swc (354.64ms)
2
+ Successfully compiled: 9 files with swc (351.19ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # @shipfox/client-invitations
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - ffd727b: Converges auth session and invitation state onto shared camelCase domain types validated at the API boundary, replacing the raw snake_case DTOs previously returned by login, signup, password reset, email verification, workspace creation, and invitation preview. `AuthState.user`, `useRefreshAuth()`, and `usePreviewInvitation()` now resolve to `UserIdentity`/`AuthenticatedSession`/`InvitationPreview` shapes (for example `accessToken` instead of `token`, `workspaceName` instead of `workspace_name`). Also moves the shared `AuthShell` component and session mapping helpers into `@shipfox/client-shell`, breaking the former `client-auth` ↔ `client-invitations` circular dependency.
8
+
9
+ ### Patch Changes
10
+
11
+ - 79df9d1: Fixes invitation query declarations for packed TypeScript consumers.
12
+ - Updated dependencies [ffd727b]
13
+ - @shipfox/client-shell@5.0.0
14
+
15
+ ## 4.0.0
16
+
17
+ ### Patch Changes
18
+
19
+ - 2e5b718: Adds safe browser persistence and bounded callback deduplication across client flows.
20
+ - Updated dependencies [2e5b718]
21
+ - Updated dependencies [769d919]
22
+ - Updated dependencies [6b4a575]
23
+ - Updated dependencies [20e4feb]
24
+ - Updated dependencies [11b10f7]
25
+ - Updated dependencies [781a45b]
26
+ - @shipfox/client-ui@4.0.0
27
+ - @shipfox/client-shell@4.0.0
28
+ - @shipfox/api-auth-dto@7.1.0
29
+ - @shipfox/client-api@4.0.0
30
+ - @shipfox/client-auth@4.0.0
31
+
3
32
  ## 3.0.1
4
33
 
5
34
  ### Patch Changes
@@ -6,7 +6,7 @@ import type { NavigateOptions } from '@tanstack/react-router';
6
6
  * routes the user into the workspace home.
7
7
  *
8
8
  * `refreshAuth` is passed in so this helper stays a plain function (no hook).
9
- * Call sites construct it via `useRefreshAuth()` from `@shipfox/client-auth`.
9
+ * Call sites construct it via `useRefreshAuth()` from `@shipfox/client-shell/runtime`.
10
10
  */
11
11
  export declare function completeInvitationAcceptance(params: {
12
12
  workspaceId: string;
@@ -6,7 +6,7 @@ import { toast } from '@shipfox/react-ui/toast';
6
6
  * routes the user into the workspace home.
7
7
  *
8
8
  * `refreshAuth` is passed in so this helper stays a plain function (no hook).
9
- * Call sites construct it via `useRefreshAuth()` from `@shipfox/client-auth`.
9
+ * Call sites construct it via `useRefreshAuth()` from `@shipfox/client-shell/runtime`.
10
10
  */ export async function completeInvitationAcceptance(params) {
11
11
  // Access tokens embed memberships at issue time, so refresh before AuthGuard
12
12
  // reads the accepted workspace.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/complete-acceptance.ts"],"sourcesContent":["import {toast} from '@shipfox/react-ui/toast';\nimport type {NavigateOptions} from '@tanstack/react-router';\n\n/**\n * Final step of every successful invitation accept path (existing-user match,\n * signup-with-invitation success, login-then-accept). Refreshes the auth\n * session so the JWT carries the new membership before navigating, then\n * routes the user into the workspace home.\n *\n * `refreshAuth` is passed in so this helper stays a plain function (no hook).\n * Call sites construct it via `useRefreshAuth()` from `@shipfox/client-auth`.\n */\nexport async function completeInvitationAcceptance(params: {\n workspaceId: string;\n workspaceName: string;\n refreshAuth: () => Promise<unknown>;\n navigate: (opts: NavigateOptions) => Promise<void> | void;\n}): Promise<void> {\n // Access tokens embed memberships at issue time, so refresh before AuthGuard\n // reads the accepted workspace.\n try {\n await params.refreshAuth();\n } catch {\n // Even if refresh fails the membership is real in the DB; surface the\n // success toast and let the user re-auth if their session has fully\n // expired. The next API call will redirect to login as usual.\n }\n toast.success(`You joined ${params.workspaceName}.`);\n await params.navigate({\n to: '/workspaces/$wid',\n params: {wid: params.workspaceId},\n });\n}\n"],"names":["toast","completeInvitationAcceptance","params","refreshAuth","success","workspaceName","navigate","to","wid","workspaceId"],"mappings":"AAAA,SAAQA,KAAK,QAAO,0BAA0B;AAG9C;;;;;;;;CAQC,GACD,OAAO,eAAeC,6BAA6BC,MAKlD;IACC,6EAA6E;IAC7E,gCAAgC;IAChC,IAAI;QACF,MAAMA,OAAOC,WAAW;IAC1B,EAAE,OAAM;IACN,sEAAsE;IACtE,oEAAoE;IACpE,8DAA8D;IAChE;IACAH,MAAMI,OAAO,CAAC,CAAC,WAAW,EAAEF,OAAOG,aAAa,CAAC,CAAC,CAAC;IACnD,MAAMH,OAAOI,QAAQ,CAAC;QACpBC,IAAI;QACJL,QAAQ;YAACM,KAAKN,OAAOO,WAAW;QAAA;IAClC;AACF"}
1
+ {"version":3,"sources":["../src/complete-acceptance.ts"],"sourcesContent":["import {toast} from '@shipfox/react-ui/toast';\nimport type {NavigateOptions} from '@tanstack/react-router';\n\n/**\n * Final step of every successful invitation accept path (existing-user match,\n * signup-with-invitation success, login-then-accept). Refreshes the auth\n * session so the JWT carries the new membership before navigating, then\n * routes the user into the workspace home.\n *\n * `refreshAuth` is passed in so this helper stays a plain function (no hook).\n * Call sites construct it via `useRefreshAuth()` from `@shipfox/client-shell/runtime`.\n */\nexport async function completeInvitationAcceptance(params: {\n workspaceId: string;\n workspaceName: string;\n refreshAuth: () => Promise<unknown>;\n navigate: (opts: NavigateOptions) => Promise<void> | void;\n}): Promise<void> {\n // Access tokens embed memberships at issue time, so refresh before AuthGuard\n // reads the accepted workspace.\n try {\n await params.refreshAuth();\n } catch {\n // Even if refresh fails the membership is real in the DB; surface the\n // success toast and let the user re-auth if their session has fully\n // expired. The next API call will redirect to login as usual.\n }\n toast.success(`You joined ${params.workspaceName}.`);\n await params.navigate({\n to: '/workspaces/$wid',\n params: {wid: params.workspaceId},\n });\n}\n"],"names":["toast","completeInvitationAcceptance","params","refreshAuth","success","workspaceName","navigate","to","wid","workspaceId"],"mappings":"AAAA,SAAQA,KAAK,QAAO,0BAA0B;AAG9C;;;;;;;;CAQC,GACD,OAAO,eAAeC,6BAA6BC,MAKlD;IACC,6EAA6E;IAC7E,gCAAgC;IAChC,IAAI;QACF,MAAMA,OAAOC,WAAW;IAC1B,EAAE,OAAM;IACN,sEAAsE;IACtE,oEAAoE;IACpE,8DAA8D;IAChE;IACAH,MAAMI,OAAO,CAAC,CAAC,WAAW,EAAEF,OAAOG,aAAa,CAAC,CAAC,CAAC;IACnD,MAAMH,OAAOI,QAAQ,CAAC;QACpBC,IAAI;QACJL,QAAQ;YAACM,KAAKN,OAAOO,WAAW;QAAA;IAClC;AACF"}
@@ -0,0 +1,21 @@
1
+ export type InvitationPreview = {
2
+ status: 'pending';
3
+ workspaceId: string;
4
+ workspaceName: string;
5
+ email: string;
6
+ invitedByDisplay?: string;
7
+ expiresAt: string;
8
+ } | {
9
+ status: 'expired';
10
+ workspaceName: string;
11
+ expiresAt: string;
12
+ } | {
13
+ status: 'already_used';
14
+ workspaceName: string;
15
+ } | {
16
+ status: 'invalid';
17
+ };
18
+ export declare function pendingInvitation(preview: InvitationPreview | undefined): Extract<InvitationPreview, {
19
+ status: 'pending';
20
+ }> | undefined;
21
+ //# sourceMappingURL=invitation-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-preview.d.ts","sourceRoot":"","sources":["../../src/core/invitation-preview.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GACzB;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,GAC7D;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAC,GAC/C;IAAC,MAAM,EAAE,SAAS,CAAA;CAAC,CAAC;AAExB,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,iBAAiB,GAAG,SAAS,GACrC,OAAO,CAAC,iBAAiB,EAAE;IAAC,MAAM,EAAE,SAAS,CAAA;CAAC,CAAC,GAAG,SAAS,CAE7D"}
@@ -0,0 +1,5 @@
1
+ export function pendingInvitation(preview) {
2
+ return preview?.status === 'pending' ? preview : undefined;
3
+ }
4
+
5
+ //# sourceMappingURL=invitation-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/invitation-preview.ts"],"sourcesContent":["export type InvitationPreview =\n | {\n status: 'pending';\n workspaceId: string;\n workspaceName: string;\n email: string;\n invitedByDisplay?: string;\n expiresAt: string;\n }\n | {status: 'expired'; workspaceName: string; expiresAt: string}\n | {status: 'already_used'; workspaceName: string}\n | {status: 'invalid'};\n\nexport function pendingInvitation(\n preview: InvitationPreview | undefined,\n): Extract<InvitationPreview, {status: 'pending'}> | undefined {\n return preview?.status === 'pending' ? preview : undefined;\n}\n"],"names":["pendingInvitation","preview","status","undefined"],"mappings":"AAaA,OAAO,SAASA,kBACdC,OAAsC;IAEtC,OAAOA,SAASC,WAAW,YAAYD,UAAUE;AACnD"}
@@ -0,0 +1,4 @@
1
+ import type { PreviewInvitationResponseDto } from '@shipfox/api-workspaces-dto';
2
+ import type { InvitationPreview } from '#core/invitation-preview.js';
3
+ export declare function toInvitationPreview(dto: PreviewInvitationResponseDto): InvitationPreview;
4
+ //# sourceMappingURL=invitation-preview-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-preview-mapper.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/invitation-preview-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,4BAA4B,EAAC,MAAM,6BAA6B,CAAC;AAC9E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAEnE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,4BAA4B,GAAG,iBAAiB,CAkBxF"}
@@ -0,0 +1,32 @@
1
+ export function toInvitationPreview(dto) {
2
+ switch(dto.status){
3
+ case 'pending':
4
+ return {
5
+ status: dto.status,
6
+ workspaceId: dto.workspace_id,
7
+ workspaceName: dto.workspace_name,
8
+ email: dto.email,
9
+ ...dto.invited_by_display ? {
10
+ invitedByDisplay: dto.invited_by_display
11
+ } : {},
12
+ expiresAt: dto.expires_at
13
+ };
14
+ case 'expired':
15
+ return {
16
+ status: dto.status,
17
+ workspaceName: dto.workspace_name,
18
+ expiresAt: dto.expires_at
19
+ };
20
+ case 'already_used':
21
+ return {
22
+ status: dto.status,
23
+ workspaceName: dto.workspace_name
24
+ };
25
+ case 'invalid':
26
+ return {
27
+ status: dto.status
28
+ };
29
+ }
30
+ }
31
+
32
+ //# sourceMappingURL=invitation-preview-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/api/invitation-preview-mapper.ts"],"sourcesContent":["import type {PreviewInvitationResponseDto} from '@shipfox/api-workspaces-dto';\nimport type {InvitationPreview} from '#core/invitation-preview.js';\n\nexport function toInvitationPreview(dto: PreviewInvitationResponseDto): InvitationPreview {\n switch (dto.status) {\n case 'pending':\n return {\n status: dto.status,\n workspaceId: dto.workspace_id,\n workspaceName: dto.workspace_name,\n email: dto.email,\n ...(dto.invited_by_display ? {invitedByDisplay: dto.invited_by_display} : {}),\n expiresAt: dto.expires_at,\n };\n case 'expired':\n return {status: dto.status, workspaceName: dto.workspace_name, expiresAt: dto.expires_at};\n case 'already_used':\n return {status: dto.status, workspaceName: dto.workspace_name};\n case 'invalid':\n return {status: dto.status};\n }\n}\n"],"names":["toInvitationPreview","dto","status","workspaceId","workspace_id","workspaceName","workspace_name","email","invited_by_display","invitedByDisplay","expiresAt","expires_at"],"mappings":"AAGA,OAAO,SAASA,oBAAoBC,GAAiC;IACnE,OAAQA,IAAIC,MAAM;QAChB,KAAK;YACH,OAAO;gBACLA,QAAQD,IAAIC,MAAM;gBAClBC,aAAaF,IAAIG,YAAY;gBAC7BC,eAAeJ,IAAIK,cAAc;gBACjCC,OAAON,IAAIM,KAAK;gBAChB,GAAIN,IAAIO,kBAAkB,GAAG;oBAACC,kBAAkBR,IAAIO,kBAAkB;gBAAA,IAAI,CAAC,CAAC;gBAC5EE,WAAWT,IAAIU,UAAU;YAC3B;QACF,KAAK;YACH,OAAO;gBAACT,QAAQD,IAAIC,MAAM;gBAAEG,eAAeJ,IAAIK,cAAc;gBAAEI,WAAWT,IAAIU,UAAU;YAAA;QAC1F,KAAK;YACH,OAAO;gBAACT,QAAQD,IAAIC,MAAM;gBAAEG,eAAeJ,IAAIK,cAAc;YAAA;QAC/D,KAAK;YACH,OAAO;gBAACJ,QAAQD,IAAIC,MAAM;YAAA;IAC9B;AACF"}
@@ -1,19 +1,24 @@
1
+ import { type FetchQueryOptions } from '@tanstack/react-query';
2
+ import type { InvitationPreview } from '#core/invitation-preview.js';
1
3
  export declare const previewInvitationQueryKey: (token: string) => readonly ["invitations", "preview", string];
4
+ type PreviewInvitationQueryOptions = FetchQueryOptions<InvitationPreview, Error, InvitationPreview, ReturnType<typeof previewInvitationQueryKey>>;
5
+ export declare function previewInvitationQueryOptions(token: string): PreviewInvitationQueryOptions;
2
6
  export declare function usePreviewInvitation(token: string | undefined): import("@tanstack/react-query").UseQueryResult<NoInfer<{
3
7
  status: "pending";
4
- workspace_id: string;
5
- workspace_name: string;
8
+ workspaceId: string;
9
+ workspaceName: string;
6
10
  email: string;
7
- invited_by_display: string | null;
8
- expires_at: string;
11
+ invitedByDisplay?: string;
12
+ expiresAt: string;
9
13
  } | {
10
14
  status: "expired";
11
- workspace_name: string;
12
- expires_at: string;
15
+ workspaceName: string;
16
+ expiresAt: string;
13
17
  } | {
14
18
  status: "already_used";
15
- workspace_name: string;
19
+ workspaceName: string;
16
20
  } | {
17
21
  status: "invalid";
18
22
  }>, Error>;
23
+ export {};
19
24
  //# sourceMappingURL=preview-invitation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preview-invitation.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/preview-invitation.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,yBAAyB,GAAI,OAAO,MAAM,gDACX,CAAC;AAS7C,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;;;;;;;;;;;;;;;;WAQ7D"}
1
+ {"version":3,"file":"preview-invitation.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/preview-invitation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,iBAAiB,EAAyB,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAGnE,eAAO,MAAM,yBAAyB,GAAI,OAAO,MAAM,gDACX,CAAC;AAE7C,KAAK,6BAA6B,GAAG,iBAAiB,CACpD,iBAAiB,EACjB,KAAK,EACL,iBAAiB,EACjB,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAC7C,CAAC;AAWF,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,6BAA6B,CAQ1F;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS;;;;;;;;;;;;;;;;WAE7D"}
@@ -1,5 +1,7 @@
1
- import { apiRequest } from '@shipfox/client-api';
2
- import { useQuery } from '@tanstack/react-query';
1
+ import { previewInvitationResponseSchema } from '@shipfox/api-workspaces-dto';
2
+ import { checkedApiRequest } from '@shipfox/client-api';
3
+ import { queryOptions, useQuery } from '@tanstack/react-query';
4
+ import { toInvitationPreview } from './invitation-preview-mapper.js';
3
5
  export const previewInvitationQueryKey = (token)=>[
4
6
  'invitations',
5
7
  'preview',
@@ -9,16 +11,20 @@ async function previewInvitation(token) {
9
11
  const params = new URLSearchParams({
10
12
  token
11
13
  });
12
- return await apiRequest(`/invitations/preview?${params.toString()}`);
14
+ const response = await checkedApiRequest(previewInvitationResponseSchema, `/invitations/preview?${params.toString()}`);
15
+ return toInvitationPreview(response);
13
16
  }
14
- export function usePreviewInvitation(token) {
15
- return useQuery({
16
- queryKey: previewInvitationQueryKey(token ?? ''),
17
+ export function previewInvitationQueryOptions(token) {
18
+ return queryOptions({
19
+ queryKey: previewInvitationQueryKey(token),
17
20
  queryFn: ()=>previewInvitation(token),
18
21
  enabled: Boolean(token),
19
22
  retry: false,
20
23
  staleTime: 30_000
21
24
  });
22
25
  }
26
+ export function usePreviewInvitation(token) {
27
+ return useQuery(previewInvitationQueryOptions(token ?? ''));
28
+ }
23
29
 
24
30
  //# sourceMappingURL=preview-invitation.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/hooks/api/preview-invitation.ts"],"sourcesContent":["import type {PreviewInvitationResponseDto} from '@shipfox/api-workspaces-dto';\nimport {apiRequest} from '@shipfox/client-api';\nimport {useQuery} from '@tanstack/react-query';\n\nexport const previewInvitationQueryKey = (token: string) =>\n ['invitations', 'preview', token] as const;\n\nasync function previewInvitation(token: string): Promise<PreviewInvitationResponseDto> {\n const params = new URLSearchParams({token});\n return await apiRequest<PreviewInvitationResponseDto>(\n `/invitations/preview?${params.toString()}`,\n );\n}\n\nexport function usePreviewInvitation(token: string | undefined) {\n return useQuery({\n queryKey: previewInvitationQueryKey(token ?? ''),\n queryFn: () => previewInvitation(token as string),\n enabled: Boolean(token),\n retry: false,\n staleTime: 30_000,\n });\n}\n"],"names":["apiRequest","useQuery","previewInvitationQueryKey","token","previewInvitation","params","URLSearchParams","toString","usePreviewInvitation","queryKey","queryFn","enabled","Boolean","retry","staleTime"],"mappings":"AACA,SAAQA,UAAU,QAAO,sBAAsB;AAC/C,SAAQC,QAAQ,QAAO,wBAAwB;AAE/C,OAAO,MAAMC,4BAA4B,CAACC,QACxC;QAAC;QAAe;QAAWA;KAAM,CAAU;AAE7C,eAAeC,kBAAkBD,KAAa;IAC5C,MAAME,SAAS,IAAIC,gBAAgB;QAACH;IAAK;IACzC,OAAO,MAAMH,WACX,CAAC,qBAAqB,EAAEK,OAAOE,QAAQ,IAAI;AAE/C;AAEA,OAAO,SAASC,qBAAqBL,KAAyB;IAC5D,OAAOF,SAAS;QACdQ,UAAUP,0BAA0BC,SAAS;QAC7CO,SAAS,IAAMN,kBAAkBD;QACjCQ,SAASC,QAAQT;QACjBU,OAAO;QACPC,WAAW;IACb;AACF"}
1
+ {"version":3,"sources":["../../../src/hooks/api/preview-invitation.ts"],"sourcesContent":["import {previewInvitationResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {type FetchQueryOptions, queryOptions, useQuery} from '@tanstack/react-query';\nimport type {InvitationPreview} from '#core/invitation-preview.js';\nimport {toInvitationPreview} from './invitation-preview-mapper.js';\n\nexport const previewInvitationQueryKey = (token: string) =>\n ['invitations', 'preview', token] as const;\n\ntype PreviewInvitationQueryOptions = FetchQueryOptions<\n InvitationPreview,\n Error,\n InvitationPreview,\n ReturnType<typeof previewInvitationQueryKey>\n>;\n\nasync function previewInvitation(token: string): Promise<InvitationPreview> {\n const params = new URLSearchParams({token});\n const response = await checkedApiRequest(\n previewInvitationResponseSchema,\n `/invitations/preview?${params.toString()}`,\n );\n return toInvitationPreview(response);\n}\n\nexport function previewInvitationQueryOptions(token: string): PreviewInvitationQueryOptions {\n return queryOptions({\n queryKey: previewInvitationQueryKey(token),\n queryFn: () => previewInvitation(token),\n enabled: Boolean(token),\n retry: false,\n staleTime: 30_000,\n });\n}\n\nexport function usePreviewInvitation(token: string | undefined) {\n return useQuery(previewInvitationQueryOptions(token ?? ''));\n}\n"],"names":["previewInvitationResponseSchema","checkedApiRequest","queryOptions","useQuery","toInvitationPreview","previewInvitationQueryKey","token","previewInvitation","params","URLSearchParams","response","toString","previewInvitationQueryOptions","queryKey","queryFn","enabled","Boolean","retry","staleTime","usePreviewInvitation"],"mappings":"AAAA,SAAQA,+BAA+B,QAAO,8BAA8B;AAC5E,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAgCC,YAAY,EAAEC,QAAQ,QAAO,wBAAwB;AAErF,SAAQC,mBAAmB,QAAO,iCAAiC;AAEnE,OAAO,MAAMC,4BAA4B,CAACC,QACxC;QAAC;QAAe;QAAWA;KAAM,CAAU;AAS7C,eAAeC,kBAAkBD,KAAa;IAC5C,MAAME,SAAS,IAAIC,gBAAgB;QAACH;IAAK;IACzC,MAAMI,WAAW,MAAMT,kBACrBD,iCACA,CAAC,qBAAqB,EAAEQ,OAAOG,QAAQ,IAAI;IAE7C,OAAOP,oBAAoBM;AAC7B;AAEA,OAAO,SAASE,8BAA8BN,KAAa;IACzD,OAAOJ,aAAa;QAClBW,UAAUR,0BAA0BC;QACpCQ,SAAS,IAAMP,kBAAkBD;QACjCS,SAASC,QAAQV;QACjBW,OAAO;QACPC,WAAW;IACb;AACF;AAEA,OAAO,SAASC,qBAAqBb,KAAyB;IAC5D,OAAOH,SAASS,8BAA8BN,SAAS;AACzD"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ export { type InvitationPreview, pendingInvitation } from '#core/invitation-preview.js';
1
2
  export { completeInvitationAcceptance } from './complete-acceptance.js';
2
3
  export { useAcceptInvitation } from './hooks/api/accept-invitation.js';
3
- export { previewInvitationQueryKey, usePreviewInvitation, } from './hooks/api/preview-invitation.js';
4
+ export { previewInvitationQueryKey, previewInvitationQueryOptions, usePreviewInvitation, } from './hooks/api/preview-invitation.js';
4
5
  export { InvitationAcceptPage } from './pages/invitation-accept-page.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,4BAA4B,EAAC,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAC,mBAAmB,EAAC,MAAM,kCAAkC,CAAC;AACrE,OAAO,EACL,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,oBAAoB,EAAC,MAAM,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AACtF,OAAO,EAAC,4BAA4B,EAAC,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAC,mBAAmB,EAAC,MAAM,kCAAkC,CAAC;AACrE,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAC,oBAAoB,EAAC,MAAM,mCAAmC,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
+ export { pendingInvitation } from '#core/invitation-preview.js';
1
2
  export { completeInvitationAcceptance } from './complete-acceptance.js';
2
3
  export { useAcceptInvitation } from './hooks/api/accept-invitation.js';
3
- export { previewInvitationQueryKey, usePreviewInvitation } from './hooks/api/preview-invitation.js';
4
+ export { previewInvitationQueryKey, previewInvitationQueryOptions, usePreviewInvitation } from './hooks/api/preview-invitation.js';
4
5
  export { InvitationAcceptPage } from './pages/invitation-accept-page.js';
5
6
 
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {completeInvitationAcceptance} from './complete-acceptance.js';\nexport {useAcceptInvitation} from './hooks/api/accept-invitation.js';\nexport {\n previewInvitationQueryKey,\n usePreviewInvitation,\n} from './hooks/api/preview-invitation.js';\nexport {InvitationAcceptPage} from './pages/invitation-accept-page.js';\n"],"names":["completeInvitationAcceptance","useAcceptInvitation","previewInvitationQueryKey","usePreviewInvitation","InvitationAcceptPage"],"mappings":"AAAA,SAAQA,4BAA4B,QAAO,2BAA2B;AACtE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SACEC,yBAAyB,EACzBC,oBAAoB,QACf,oCAAoC;AAC3C,SAAQC,oBAAoB,QAAO,oCAAoC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {type InvitationPreview, pendingInvitation} from '#core/invitation-preview.js';\nexport {completeInvitationAcceptance} from './complete-acceptance.js';\nexport {useAcceptInvitation} from './hooks/api/accept-invitation.js';\nexport {\n previewInvitationQueryKey,\n previewInvitationQueryOptions,\n usePreviewInvitation,\n} from './hooks/api/preview-invitation.js';\nexport {InvitationAcceptPage} from './pages/invitation-accept-page.js';\n"],"names":["pendingInvitation","completeInvitationAcceptance","useAcceptInvitation","previewInvitationQueryKey","previewInvitationQueryOptions","usePreviewInvitation","InvitationAcceptPage"],"mappings":"AAAA,SAAgCA,iBAAiB,QAAO,8BAA8B;AACtF,SAAQC,4BAA4B,QAAO,2BAA2B;AACtE,SAAQC,mBAAmB,QAAO,mCAAmC;AACrE,SACEC,yBAAyB,EACzBC,6BAA6B,EAC7BC,oBAAoB,QACf,oCAAoC;AAC3C,SAAQC,oBAAoB,QAAO,oCAAoC"}
@@ -1 +1 @@
1
- {"version":3,"file":"invitation-accept-page.d.ts","sourceRoot":"","sources":["../../src/pages/invitation-accept-page.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,oBAAoB,uCAkPnC"}
1
+ {"version":3,"file":"invitation-accept-page.d.ts","sourceRoot":"","sources":["../../src/pages/invitation-accept-page.tsx"],"names":[],"mappings":"AAkBA,wBAAgB,oBAAoB,uCA6OnC"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AuthShell, useAuthState, useRefreshAuth } from '@shipfox/client-auth';
2
+ import { AuthShell, useAuthState, useRefreshAuth } from '@shipfox/client-shell/runtime';
3
+ import { createSingleFlight } from '@shipfox/client-ui';
3
4
  import { Button } from '@shipfox/react-ui/button';
4
5
  import { Callout } from '@shipfox/react-ui/callout';
5
6
  import { ShipfoxLoader } from '@shipfox/react-ui/loader';
@@ -11,9 +12,7 @@ import { useCallback, useEffect, useRef } from 'react';
11
12
  import { completeInvitationAcceptance } from '#complete-acceptance.js';
12
13
  import { useAcceptInvitation } from '#hooks/api/accept-invitation.js';
13
14
  import { usePreviewInvitation } from '#hooks/api/preview-invitation.js';
14
- // Module-scoped guard so React StrictMode's double-mount in dev does not
15
- // fire the accept mutation twice for the same token.
16
- const inFlightAccepts = new Set();
15
+ const invitationAccepts = createSingleFlight();
17
16
  const toastedTerminals = new Set();
18
17
  const TOASTED_TERMINALS_MAX = 32;
19
18
  export function InvitationAcceptPage() {
@@ -69,7 +68,6 @@ export function InvitationAcceptPage() {
69
68
  if (!token) return;
70
69
  if (auth.isLoading) return;
71
70
  if (hasKickedAccept.current) return;
72
- if (inFlightAccepts.has(token)) return;
73
71
  const data = preview.data;
74
72
  if (!data) return;
75
73
  if (data.status !== 'pending') return;
@@ -77,15 +75,12 @@ export function InvitationAcceptPage() {
77
75
  const authedEmail = auth.user?.email?.toLowerCase();
78
76
  if (authedEmail !== data.email.toLowerCase()) return;
79
77
  hasKickedAccept.current = true;
80
- inFlightAccepts.add(token);
81
- runAccept({
82
- token,
83
- workspaceName: data.workspace_name
84
- }).catch(()=>{
78
+ invitationAccepts.run(token, async ()=>await runAccept({
79
+ token,
80
+ workspaceName: data.workspaceName
81
+ })).catch(()=>{
85
82
  // The mutation surfaces its error via accept.error; the UI re-renders
86
83
  // into the auth-match-error state below.
87
- }).finally(()=>{
88
- inFlightAccepts.delete(token);
89
84
  });
90
85
  }, [
91
86
  auth.isAuthenticated,
@@ -171,7 +166,7 @@ export function InvitationAcceptPage() {
171
166
  type: "error",
172
167
  children: [
173
168
  "This invitation expired on ",
174
- formatDate(data.expires_at),
169
+ formatDate(data.expiresAt),
175
170
  ". Ask your administrator to send a new one."
176
171
  ]
177
172
  }),
@@ -185,7 +180,7 @@ export function InvitationAcceptPage() {
185
180
  notifyOnce(`already-used:${token}`, ()=>toast.info(`This invitation has already been accepted.`));
186
181
  return /*#__PURE__*/ _jsxs(AuthShell, {
187
182
  title: "Invitation already used",
188
- description: data.workspace_name,
183
+ description: data.workspaceName,
189
184
  children: [
190
185
  /*#__PURE__*/ _jsxs(Callout, {
191
186
  role: "alert",
@@ -193,7 +188,7 @@ export function InvitationAcceptPage() {
193
188
  children: [
194
189
  "This invitation has already been accepted.",
195
190
  ' ',
196
- auth.isAuthenticated ? `Open your dashboard to access ${data.workspace_name} if your account is a member.` : `Log in to access ${data.workspace_name}.`
191
+ auth.isAuthenticated ? `Open your dashboard to access ${data.workspaceName} if your account is a member.` : `Log in to access ${data.workspaceName}.`
197
192
  ]
198
193
  }),
199
194
  auth.isAuthenticated ? null : /*#__PURE__*/ _jsx(Button, {
@@ -211,13 +206,13 @@ export function InvitationAcceptPage() {
211
206
  ]
212
207
  });
213
208
  }
214
- const inviterLine = data.invited_by_display != null ? `Invited by ${data.invited_by_display} to join as ${data.email}.` : `Invited to join as ${data.email}.`;
209
+ const inviterLine = data.invitedByDisplay != null ? `Invited by ${data.invitedByDisplay} to join as ${data.email}.` : `Invited to join as ${data.email}.`;
215
210
  if (!auth.isAuthenticated) {
216
211
  const redirect = `/invitations/accept?token=${encodeURIComponent(token)}`;
217
212
  const signupHref = `/auth/signup?redirect=${encodeURIComponent(redirect)}`;
218
213
  const loginHref = `/auth/login?redirect=${encodeURIComponent(redirect)}`;
219
214
  return /*#__PURE__*/ _jsxs(AuthShell, {
220
- title: data.workspace_name,
215
+ title: data.workspaceName,
221
216
  description: inviterLine,
222
217
  children: [
223
218
  /*#__PURE__*/ _jsxs("div", {
@@ -260,7 +255,7 @@ export function InvitationAcceptPage() {
260
255
  const redirect = `/invitations/accept?token=${encodeURIComponent(token)}`;
261
256
  const logoutHref = `/auth/logout?redirect=${encodeURIComponent(redirect)}`;
262
257
  return /*#__PURE__*/ _jsxs(AuthShell, {
263
- title: data.workspace_name,
258
+ title: data.workspaceName,
264
259
  description: inviterLine,
265
260
  children: [
266
261
  /*#__PURE__*/ _jsxs(Callout, {
@@ -303,7 +298,7 @@ export function InvitationAcceptPage() {
303
298
  // result. Show either the pending state or the error state.
304
299
  if (accept.isError) {
305
300
  return /*#__PURE__*/ _jsxs(AuthShell, {
306
- title: data.workspace_name,
301
+ title: data.workspaceName,
307
302
  description: inviterLine,
308
303
  children: [
309
304
  /*#__PURE__*/ _jsxs(Callout, {
@@ -311,7 +306,7 @@ export function InvitationAcceptPage() {
311
306
  type: "error",
312
307
  children: [
313
308
  "We couldn't add you to ",
314
- data.workspace_name,
309
+ data.workspaceName,
315
310
  ". Try again in a moment."
316
311
  ]
317
312
  }),
@@ -321,7 +316,7 @@ export function InvitationAcceptPage() {
321
316
  onClick: ()=>{
322
317
  runAccept({
323
318
  token,
324
- workspaceName: data.workspace_name
319
+ workspaceName: data.workspaceName
325
320
  }).catch(()=>{
326
321
  // The mutation state drives the rendered error; avoid an
327
322
  // unhandled rejection from the click handler.
@@ -333,7 +328,7 @@ export function InvitationAcceptPage() {
333
328
  });
334
329
  }
335
330
  return /*#__PURE__*/ _jsx(AuthShell, {
336
- title: data.workspace_name,
331
+ title: data.workspaceName,
337
332
  description: inviterLine,
338
333
  children: /*#__PURE__*/ _jsxs("div", {
339
334
  className: "flex flex-col items-center gap-12",
@@ -345,7 +340,7 @@ export function InvitationAcceptPage() {
345
340
  className: "text-foreground-neutral-subtle",
346
341
  children: [
347
342
  "Adding you to ",
348
- data.workspace_name,
343
+ data.workspaceName,
349
344
  "…"
350
345
  ]
351
346
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/invitation-accept-page.tsx"],"sourcesContent":["import {AuthShell, useAuthState, useRefreshAuth} from '@shipfox/client-auth';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {ShipfoxLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {formatDate} from '@shipfox/react-ui/utils';\nimport {Link, useNavigate, useSearch} from '@tanstack/react-router';\nimport {useCallback, useEffect, useRef} from 'react';\nimport {completeInvitationAcceptance} from '#complete-acceptance.js';\nimport {useAcceptInvitation} from '#hooks/api/accept-invitation.js';\nimport {usePreviewInvitation} from '#hooks/api/preview-invitation.js';\n\n// Module-scoped guard so React StrictMode's double-mount in dev does not\n// fire the accept mutation twice for the same token.\nconst inFlightAccepts = new Set<string>();\nconst toastedTerminals = new Set<string>();\nconst TOASTED_TERMINALS_MAX = 32;\n\nexport function InvitationAcceptPage() {\n const search = useSearch({strict: false}) as {token?: unknown};\n const token =\n typeof search.token === 'string' && search.token.length > 0 ? search.token : undefined;\n const navigate = useNavigate();\n const auth = useAuthState();\n const refreshAuth = useRefreshAuth();\n const preview = usePreviewInvitation(token);\n const accept = useAcceptInvitation();\n const hasKickedAccept = useRef(false);\n\n useEffect(() => {\n if (!token) {\n toast.error('This invitation link is missing a token.');\n const timeout = window.setTimeout(() => {\n navigate({to: '/', replace: true});\n }, 2000);\n return () => window.clearTimeout(timeout);\n }\n return undefined;\n }, [navigate, token]);\n\n const completeAccept = useCallback(\n async (workspaceId: string, workspaceName: string) => {\n await completeInvitationAcceptance({\n workspaceId,\n workspaceName,\n refreshAuth,\n navigate,\n });\n },\n [navigate, refreshAuth],\n );\n\n const runAccept = useCallback(\n async (params: {token: string; workspaceName: string}) => {\n const result = await accept.mutateAsync({token: params.token});\n await completeAccept(result.membership.workspace_id, params.workspaceName);\n },\n [accept, completeAccept],\n );\n\n // Auto-accept when authenticated and emails match (also handles already_used\n // for the canonical authenticated path: backend returns already_member=true).\n useEffect(() => {\n if (!token) return;\n if (auth.isLoading) return;\n if (hasKickedAccept.current) return;\n if (inFlightAccepts.has(token)) return;\n const data = preview.data;\n if (!data) return;\n if (data.status !== 'pending') return;\n if (!auth.isAuthenticated) return;\n\n const authedEmail = auth.user?.email?.toLowerCase();\n if (authedEmail !== data.email.toLowerCase()) return;\n\n hasKickedAccept.current = true;\n inFlightAccepts.add(token);\n\n runAccept({token, workspaceName: data.workspace_name})\n .catch(() => {\n // The mutation surfaces its error via accept.error; the UI re-renders\n // into the auth-match-error state below.\n })\n .finally(() => {\n inFlightAccepts.delete(token);\n });\n }, [auth.isAuthenticated, auth.isLoading, auth.user?.email, preview.data, runAccept, token]);\n\n if (!token) {\n return (\n <AuthShell title=\"Invalid link\" description=\"This invitation link is incomplete.\">\n <Callout role=\"alert\" type=\"error\">\n This invitation link is missing a token. Ask your administrator to resend it.\n </Callout>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-muted\" aria-live=\"polite\">\n Redirecting to the dashboard…\n </Text>\n </AuthShell>\n );\n }\n\n if (preview.isLoading) {\n return (\n <AuthShell title=\"Loading invitation\" description=\"One moment please.\">\n <CenteredLoader />\n </AuthShell>\n );\n }\n\n if (preview.isError) {\n return (\n <AuthShell title=\"Couldn't load invitation\" description=\"Try again in a moment.\">\n <Callout role=\"alert\" type=\"error\">\n We couldn't reach the server. Check your connection and retry.\n </Callout>\n <Button className=\"w-full\" onClick={() => preview.refetch()}>\n Try again\n </Button>\n </AuthShell>\n );\n }\n\n const data = preview.data;\n if (!data) {\n return null;\n }\n\n if (data.status === 'invalid') {\n notifyOnce(`invalid:${token}`, () => toast.error('This invitation link is no longer valid.'));\n return (\n <AuthShell title=\"Invalid invitation\" description=\"The link you used didn't work.\">\n <Callout role=\"alert\" type=\"error\">\n This invitation link is not valid. Ask your administrator to send a new one.\n </Callout>\n <GoHomeButton navigate={navigate} />\n </AuthShell>\n );\n }\n\n if (data.status === 'expired') {\n notifyOnce(`expired:${token}`, () => toast.error('This invitation has expired.'));\n return (\n <AuthShell title=\"Invitation expired\" description=\"The link is no longer accepting joins.\">\n <Callout role=\"alert\" type=\"error\">\n This invitation expired on {formatDate(data.expires_at)}. Ask your administrator to send a\n new one.\n </Callout>\n <GoHomeButton navigate={navigate} />\n </AuthShell>\n );\n }\n\n if (data.status === 'already_used') {\n notifyOnce(`already-used:${token}`, () =>\n toast.info(`This invitation has already been accepted.`),\n );\n return (\n <AuthShell title=\"Invitation already used\" description={data.workspace_name}>\n <Callout role=\"alert\" type=\"info\">\n This invitation has already been accepted.{' '}\n {auth.isAuthenticated\n ? `Open your dashboard to access ${data.workspace_name} if your account is a member.`\n : `Log in to access ${data.workspace_name}.`}\n </Callout>\n {auth.isAuthenticated ? null : (\n <Button asChild className=\"w-full\">\n <Link to=\"/auth/login\">Log in</Link>\n </Button>\n )}\n <GoHomeButton navigate={navigate} secondary />\n </AuthShell>\n );\n }\n\n const inviterLine =\n data.invited_by_display != null\n ? `Invited by ${data.invited_by_display} to join as ${data.email}.`\n : `Invited to join as ${data.email}.`;\n\n if (!auth.isAuthenticated) {\n const redirect = `/invitations/accept?token=${encodeURIComponent(token)}`;\n const signupHref = `/auth/signup?redirect=${encodeURIComponent(redirect)}`;\n const loginHref = `/auth/login?redirect=${encodeURIComponent(redirect)}`;\n return (\n <AuthShell title={data.workspace_name} description={inviterLine}>\n <div className=\"flex flex-col gap-16\">\n <Button asChild className=\"w-full\">\n <Link to={signupHref}>Create account</Link>\n </Button>\n <Button asChild className=\"w-full\" variant=\"secondary\">\n <Link to={loginHref}>I already have an account</Link>\n </Button>\n </div>\n <Text size=\"xs\" className=\"text-center text-foreground-neutral-muted\">\n Not {data.email}? Contact your administrator.\n </Text>\n </AuthShell>\n );\n }\n\n const viewerEmail = auth.user?.email?.toLowerCase();\n const inviteeEmail = data.email.toLowerCase();\n if (viewerEmail !== inviteeEmail) {\n const redirect = `/invitations/accept?token=${encodeURIComponent(token)}`;\n const logoutHref = `/auth/logout?redirect=${encodeURIComponent(redirect)}`;\n return (\n <AuthShell title={data.workspace_name} description={inviterLine}>\n <Callout role=\"alert\" type=\"warning\">\n You're signed in as {auth.user?.email}, but this invitation is for {data.email}.\n </Callout>\n <div className=\"flex flex-col gap-16\">\n <Button asChild className=\"w-full\">\n <Link to={logoutHref}>Log out and continue</Link>\n </Button>\n <Button\n className=\"w-full\"\n variant=\"secondary\"\n onClick={() => navigate({to: '/', replace: true})}\n >\n Cancel\n </Button>\n </div>\n </AuthShell>\n );\n }\n\n // Authenticated + matches — auto-accept is in flight or about to render its\n // result. Show either the pending state or the error state.\n if (accept.isError) {\n return (\n <AuthShell title={data.workspace_name} description={inviterLine}>\n <Callout role=\"alert\" type=\"error\">\n We couldn't add you to {data.workspace_name}. Try again in a moment.\n </Callout>\n <Button\n className=\"w-full\"\n isLoading={accept.isPending}\n onClick={() => {\n runAccept({token, workspaceName: data.workspace_name}).catch(() => {\n // The mutation state drives the rendered error; avoid an\n // unhandled rejection from the click handler.\n });\n }}\n >\n Try again\n </Button>\n </AuthShell>\n );\n }\n\n return (\n <AuthShell title={data.workspace_name} description={inviterLine}>\n <div className=\"flex flex-col items-center gap-12\" aria-live=\"polite\">\n <ShipfoxLoader />\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n Adding you to {data.workspace_name}…\n </Text>\n </div>\n </AuthShell>\n );\n}\n\nfunction CenteredLoader() {\n return (\n <div className=\"flex justify-center py-16\" aria-busy=\"true\">\n <ShipfoxLoader />\n </div>\n );\n}\n\nfunction GoHomeButton({\n navigate,\n secondary,\n}: {\n navigate: ReturnType<typeof useNavigate>;\n secondary?: boolean;\n}) {\n return (\n <Button\n className=\"w-full\"\n variant={secondary ? 'secondary' : 'primary'}\n onClick={() => navigate({to: '/', replace: true})}\n >\n Go to dashboard\n </Button>\n );\n}\n\nfunction notifyOnce(key: string, fn: () => void): void {\n if (toastedTerminals.has(key)) return;\n if (toastedTerminals.size >= TOASTED_TERMINALS_MAX) {\n const oldest = toastedTerminals.values().next().value;\n if (oldest !== undefined) toastedTerminals.delete(oldest);\n }\n toastedTerminals.add(key);\n fn();\n}\n"],"names":["AuthShell","useAuthState","useRefreshAuth","Button","Callout","ShipfoxLoader","toast","Text","formatDate","Link","useNavigate","useSearch","useCallback","useEffect","useRef","completeInvitationAcceptance","useAcceptInvitation","usePreviewInvitation","inFlightAccepts","Set","toastedTerminals","TOASTED_TERMINALS_MAX","InvitationAcceptPage","search","strict","token","length","undefined","navigate","auth","refreshAuth","preview","accept","hasKickedAccept","error","timeout","window","setTimeout","to","replace","clearTimeout","completeAccept","workspaceId","workspaceName","runAccept","params","result","mutateAsync","membership","workspace_id","isLoading","current","has","data","status","isAuthenticated","authedEmail","user","email","toLowerCase","add","workspace_name","catch","finally","delete","title","description","role","type","size","className","aria-live","CenteredLoader","isError","onClick","refetch","notifyOnce","GoHomeButton","expires_at","info","asChild","secondary","inviterLine","invited_by_display","redirect","encodeURIComponent","signupHref","loginHref","div","variant","viewerEmail","inviteeEmail","logoutHref","isPending","aria-busy","key","fn","oldest","values","next","value"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/invitation-accept-page.tsx"],"sourcesContent":["import {AuthShell, useAuthState, useRefreshAuth} from '@shipfox/client-shell/runtime';\nimport {createSingleFlight} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {ShipfoxLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {formatDate} from '@shipfox/react-ui/utils';\nimport {Link, useNavigate, useSearch} from '@tanstack/react-router';\nimport {useCallback, useEffect, useRef} from 'react';\nimport {completeInvitationAcceptance} from '#complete-acceptance.js';\nimport {useAcceptInvitation} from '#hooks/api/accept-invitation.js';\nimport {usePreviewInvitation} from '#hooks/api/preview-invitation.js';\n\nconst invitationAccepts = createSingleFlight<string, void>();\nconst toastedTerminals = new Set<string>();\nconst TOASTED_TERMINALS_MAX = 32;\n\nexport function InvitationAcceptPage() {\n const search = useSearch({strict: false}) as {token?: unknown};\n const token =\n typeof search.token === 'string' && search.token.length > 0 ? search.token : undefined;\n const navigate = useNavigate();\n const auth = useAuthState();\n const refreshAuth = useRefreshAuth();\n const preview = usePreviewInvitation(token);\n const accept = useAcceptInvitation();\n const hasKickedAccept = useRef(false);\n\n useEffect(() => {\n if (!token) {\n toast.error('This invitation link is missing a token.');\n const timeout = window.setTimeout(() => {\n navigate({to: '/', replace: true});\n }, 2000);\n return () => window.clearTimeout(timeout);\n }\n return undefined;\n }, [navigate, token]);\n\n const completeAccept = useCallback(\n async (workspaceId: string, workspaceName: string) => {\n await completeInvitationAcceptance({\n workspaceId,\n workspaceName,\n refreshAuth,\n navigate,\n });\n },\n [navigate, refreshAuth],\n );\n\n const runAccept = useCallback(\n async (params: {token: string; workspaceName: string}) => {\n const result = await accept.mutateAsync({token: params.token});\n await completeAccept(result.membership.workspace_id, params.workspaceName);\n },\n [accept, completeAccept],\n );\n\n // Auto-accept when authenticated and emails match (also handles already_used\n // for the canonical authenticated path: backend returns already_member=true).\n useEffect(() => {\n if (!token) return;\n if (auth.isLoading) return;\n if (hasKickedAccept.current) return;\n const data = preview.data;\n if (!data) return;\n if (data.status !== 'pending') return;\n if (!auth.isAuthenticated) return;\n\n const authedEmail = auth.user?.email?.toLowerCase();\n if (authedEmail !== data.email.toLowerCase()) return;\n\n hasKickedAccept.current = true;\n invitationAccepts\n .run(token, async () => await runAccept({token, workspaceName: data.workspaceName}))\n .catch(() => {\n // The mutation surfaces its error via accept.error; the UI re-renders\n // into the auth-match-error state below.\n });\n }, [auth.isAuthenticated, auth.isLoading, auth.user?.email, preview.data, runAccept, token]);\n\n if (!token) {\n return (\n <AuthShell title=\"Invalid link\" description=\"This invitation link is incomplete.\">\n <Callout role=\"alert\" type=\"error\">\n This invitation link is missing a token. Ask your administrator to resend it.\n </Callout>\n <Text size=\"sm\" className=\"text-center text-foreground-neutral-muted\" aria-live=\"polite\">\n Redirecting to the dashboard…\n </Text>\n </AuthShell>\n );\n }\n\n if (preview.isLoading) {\n return (\n <AuthShell title=\"Loading invitation\" description=\"One moment please.\">\n <CenteredLoader />\n </AuthShell>\n );\n }\n\n if (preview.isError) {\n return (\n <AuthShell title=\"Couldn't load invitation\" description=\"Try again in a moment.\">\n <Callout role=\"alert\" type=\"error\">\n We couldn't reach the server. Check your connection and retry.\n </Callout>\n <Button className=\"w-full\" onClick={() => preview.refetch()}>\n Try again\n </Button>\n </AuthShell>\n );\n }\n\n const data = preview.data;\n if (!data) {\n return null;\n }\n\n if (data.status === 'invalid') {\n notifyOnce(`invalid:${token}`, () => toast.error('This invitation link is no longer valid.'));\n return (\n <AuthShell title=\"Invalid invitation\" description=\"The link you used didn't work.\">\n <Callout role=\"alert\" type=\"error\">\n This invitation link is not valid. Ask your administrator to send a new one.\n </Callout>\n <GoHomeButton navigate={navigate} />\n </AuthShell>\n );\n }\n\n if (data.status === 'expired') {\n notifyOnce(`expired:${token}`, () => toast.error('This invitation has expired.'));\n return (\n <AuthShell title=\"Invitation expired\" description=\"The link is no longer accepting joins.\">\n <Callout role=\"alert\" type=\"error\">\n This invitation expired on {formatDate(data.expiresAt)}. Ask your administrator to send a\n new one.\n </Callout>\n <GoHomeButton navigate={navigate} />\n </AuthShell>\n );\n }\n\n if (data.status === 'already_used') {\n notifyOnce(`already-used:${token}`, () =>\n toast.info(`This invitation has already been accepted.`),\n );\n return (\n <AuthShell title=\"Invitation already used\" description={data.workspaceName}>\n <Callout role=\"alert\" type=\"info\">\n This invitation has already been accepted.{' '}\n {auth.isAuthenticated\n ? `Open your dashboard to access ${data.workspaceName} if your account is a member.`\n : `Log in to access ${data.workspaceName}.`}\n </Callout>\n {auth.isAuthenticated ? null : (\n <Button asChild className=\"w-full\">\n <Link to=\"/auth/login\">Log in</Link>\n </Button>\n )}\n <GoHomeButton navigate={navigate} secondary />\n </AuthShell>\n );\n }\n\n const inviterLine =\n data.invitedByDisplay != null\n ? `Invited by ${data.invitedByDisplay} to join as ${data.email}.`\n : `Invited to join as ${data.email}.`;\n\n if (!auth.isAuthenticated) {\n const redirect = `/invitations/accept?token=${encodeURIComponent(token)}`;\n const signupHref = `/auth/signup?redirect=${encodeURIComponent(redirect)}`;\n const loginHref = `/auth/login?redirect=${encodeURIComponent(redirect)}`;\n return (\n <AuthShell title={data.workspaceName} description={inviterLine}>\n <div className=\"flex flex-col gap-16\">\n <Button asChild className=\"w-full\">\n <Link to={signupHref}>Create account</Link>\n </Button>\n <Button asChild className=\"w-full\" variant=\"secondary\">\n <Link to={loginHref}>I already have an account</Link>\n </Button>\n </div>\n <Text size=\"xs\" className=\"text-center text-foreground-neutral-muted\">\n Not {data.email}? Contact your administrator.\n </Text>\n </AuthShell>\n );\n }\n\n const viewerEmail = auth.user?.email?.toLowerCase();\n const inviteeEmail = data.email.toLowerCase();\n if (viewerEmail !== inviteeEmail) {\n const redirect = `/invitations/accept?token=${encodeURIComponent(token)}`;\n const logoutHref = `/auth/logout?redirect=${encodeURIComponent(redirect)}`;\n return (\n <AuthShell title={data.workspaceName} description={inviterLine}>\n <Callout role=\"alert\" type=\"warning\">\n You're signed in as {auth.user?.email}, but this invitation is for {data.email}.\n </Callout>\n <div className=\"flex flex-col gap-16\">\n <Button asChild className=\"w-full\">\n <Link to={logoutHref}>Log out and continue</Link>\n </Button>\n <Button\n className=\"w-full\"\n variant=\"secondary\"\n onClick={() => navigate({to: '/', replace: true})}\n >\n Cancel\n </Button>\n </div>\n </AuthShell>\n );\n }\n\n // Authenticated + matches — auto-accept is in flight or about to render its\n // result. Show either the pending state or the error state.\n if (accept.isError) {\n return (\n <AuthShell title={data.workspaceName} description={inviterLine}>\n <Callout role=\"alert\" type=\"error\">\n We couldn't add you to {data.workspaceName}. Try again in a moment.\n </Callout>\n <Button\n className=\"w-full\"\n isLoading={accept.isPending}\n onClick={() => {\n runAccept({token, workspaceName: data.workspaceName}).catch(() => {\n // The mutation state drives the rendered error; avoid an\n // unhandled rejection from the click handler.\n });\n }}\n >\n Try again\n </Button>\n </AuthShell>\n );\n }\n\n return (\n <AuthShell title={data.workspaceName} description={inviterLine}>\n <div className=\"flex flex-col items-center gap-12\" aria-live=\"polite\">\n <ShipfoxLoader />\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n Adding you to {data.workspaceName}…\n </Text>\n </div>\n </AuthShell>\n );\n}\n\nfunction CenteredLoader() {\n return (\n <div className=\"flex justify-center py-16\" aria-busy=\"true\">\n <ShipfoxLoader />\n </div>\n );\n}\n\nfunction GoHomeButton({\n navigate,\n secondary,\n}: {\n navigate: ReturnType<typeof useNavigate>;\n secondary?: boolean;\n}) {\n return (\n <Button\n className=\"w-full\"\n variant={secondary ? 'secondary' : 'primary'}\n onClick={() => navigate({to: '/', replace: true})}\n >\n Go to dashboard\n </Button>\n );\n}\n\nfunction notifyOnce(key: string, fn: () => void): void {\n if (toastedTerminals.has(key)) return;\n if (toastedTerminals.size >= TOASTED_TERMINALS_MAX) {\n const oldest = toastedTerminals.values().next().value;\n if (oldest !== undefined) toastedTerminals.delete(oldest);\n }\n toastedTerminals.add(key);\n fn();\n}\n"],"names":["AuthShell","useAuthState","useRefreshAuth","createSingleFlight","Button","Callout","ShipfoxLoader","toast","Text","formatDate","Link","useNavigate","useSearch","useCallback","useEffect","useRef","completeInvitationAcceptance","useAcceptInvitation","usePreviewInvitation","invitationAccepts","toastedTerminals","Set","TOASTED_TERMINALS_MAX","InvitationAcceptPage","search","strict","token","length","undefined","navigate","auth","refreshAuth","preview","accept","hasKickedAccept","error","timeout","window","setTimeout","to","replace","clearTimeout","completeAccept","workspaceId","workspaceName","runAccept","params","result","mutateAsync","membership","workspace_id","isLoading","current","data","status","isAuthenticated","authedEmail","user","email","toLowerCase","run","catch","title","description","role","type","size","className","aria-live","CenteredLoader","isError","onClick","refetch","notifyOnce","GoHomeButton","expiresAt","info","asChild","secondary","inviterLine","invitedByDisplay","redirect","encodeURIComponent","signupHref","loginHref","div","variant","viewerEmail","inviteeEmail","logoutHref","isPending","aria-busy","key","fn","has","oldest","values","next","value","delete","add"],"mappings":"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"}