@shipfox/client-auth 0.2.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 (225) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +7 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.turbo/turbo-build.log +2 -0
  5. package/.turbo/turbo-type$colon$emit.log +1 -0
  6. package/.turbo/turbo-type.log +1 -0
  7. package/CHANGELOG.md +111 -0
  8. package/LICENSE +21 -0
  9. package/dist/components/auth-guard.d.ts +5 -0
  10. package/dist/components/auth-guard.d.ts.map +1 -0
  11. package/dist/components/auth-guard.js +61 -0
  12. package/dist/components/auth-guard.js.map +1 -0
  13. package/dist/components/auth-provider.d.ts +11 -0
  14. package/dist/components/auth-provider.d.ts.map +1 -0
  15. package/dist/components/auth-provider.js +21 -0
  16. package/dist/components/auth-provider.js.map +1 -0
  17. package/dist/components/auth-shell.d.ts +11 -0
  18. package/dist/components/auth-shell.d.ts.map +1 -0
  19. package/dist/components/auth-shell.js +63 -0
  20. package/dist/components/auth-shell.js.map +1 -0
  21. package/dist/components/redirect-target.d.ts +2 -0
  22. package/dist/components/redirect-target.d.ts.map +1 -0
  23. package/dist/components/redirect-target.js +22 -0
  24. package/dist/components/redirect-target.js.map +1 -0
  25. package/dist/components/workspace-crumb.d.ts +3 -0
  26. package/dist/components/workspace-crumb.d.ts.map +1 -0
  27. package/dist/components/workspace-crumb.js +3 -0
  28. package/dist/components/workspace-crumb.js.map +1 -0
  29. package/dist/components/workspace-switcher.d.ts +6 -0
  30. package/dist/components/workspace-switcher.d.ts.map +1 -0
  31. package/dist/components/workspace-switcher.js +91 -0
  32. package/dist/components/workspace-switcher.js.map +1 -0
  33. package/dist/components/workspace-switcher.stories.d.ts +26 -0
  34. package/dist/components/workspace-switcher.stories.d.ts.map +1 -0
  35. package/dist/components/workspace-switcher.stories.js +168 -0
  36. package/dist/components/workspace-switcher.stories.js.map +1 -0
  37. package/dist/feature.d.ts +33 -0
  38. package/dist/feature.d.ts.map +1 -0
  39. package/dist/feature.js +43 -0
  40. package/dist/feature.js.map +1 -0
  41. package/dist/hooks/api/login-auth.d.ts +29 -0
  42. package/dist/hooks/api/login-auth.d.ts.map +1 -0
  43. package/dist/hooks/api/login-auth.js +42 -0
  44. package/dist/hooks/api/login-auth.js.map +1 -0
  45. package/dist/hooks/api/logout-auth.d.ts +2 -0
  46. package/dist/hooks/api/logout-auth.d.ts.map +1 -0
  47. package/dist/hooks/api/logout-auth.js +32 -0
  48. package/dist/hooks/api/logout-auth.js.map +1 -0
  49. package/dist/hooks/api/password-reset-auth.d.ts +33 -0
  50. package/dist/hooks/api/password-reset-auth.d.ts.map +1 -0
  51. package/dist/hooks/api/password-reset-auth.js +48 -0
  52. package/dist/hooks/api/password-reset-auth.js.map +1 -0
  53. package/dist/hooks/api/refresh-auth.d.ts +2 -0
  54. package/dist/hooks/api/refresh-auth.d.ts.map +1 -0
  55. package/dist/hooks/api/refresh-auth.js +3 -0
  56. package/dist/hooks/api/refresh-auth.js.map +1 -0
  57. package/dist/hooks/api/signup-auth.d.ts +27 -0
  58. package/dist/hooks/api/signup-auth.d.ts.map +1 -0
  59. package/dist/hooks/api/signup-auth.js +15 -0
  60. package/dist/hooks/api/signup-auth.js.map +1 -0
  61. package/dist/hooks/api/verify-email-auth.d.ts +20 -0
  62. package/dist/hooks/api/verify-email-auth.d.ts.map +1 -0
  63. package/dist/hooks/api/verify-email-auth.js +48 -0
  64. package/dist/hooks/api/verify-email-auth.js.map +1 -0
  65. package/dist/hooks/api/workspace-auth.d.ts +22 -0
  66. package/dist/hooks/api/workspace-auth.d.ts.map +1 -0
  67. package/dist/hooks/api/workspace-auth.js +25 -0
  68. package/dist/hooks/api/workspace-auth.js.map +1 -0
  69. package/dist/hooks/index.d.ts +11 -0
  70. package/dist/hooks/index.d.ts.map +1 -0
  71. package/dist/hooks/index.js +11 -0
  72. package/dist/hooks/index.js.map +1 -0
  73. package/dist/hooks/use-active-workspace.d.ts +2 -0
  74. package/dist/hooks/use-active-workspace.d.ts.map +1 -0
  75. package/dist/hooks/use-active-workspace.js +3 -0
  76. package/dist/hooks/use-active-workspace.js.map +1 -0
  77. package/dist/hooks/use-auth-state.d.ts +2 -0
  78. package/dist/hooks/use-auth-state.d.ts.map +1 -0
  79. package/dist/hooks/use-auth-state.js +3 -0
  80. package/dist/hooks/use-auth-state.js.map +1 -0
  81. package/dist/index.d.ts +15 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +16 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/pages/email-verification-resend-model.d.ts +7 -0
  86. package/dist/pages/email-verification-resend-model.d.ts.map +1 -0
  87. package/dist/pages/email-verification-resend-model.js +15 -0
  88. package/dist/pages/email-verification-resend-model.js.map +1 -0
  89. package/dist/pages/form-errors.d.ts +19 -0
  90. package/dist/pages/form-errors.d.ts.map +1 -0
  91. package/dist/pages/form-errors.js +51 -0
  92. package/dist/pages/form-errors.js.map +1 -0
  93. package/dist/pages/form-utils.d.ts +2 -0
  94. package/dist/pages/form-utils.d.ts.map +1 -0
  95. package/dist/pages/form-utils.js +28 -0
  96. package/dist/pages/form-utils.js.map +1 -0
  97. package/dist/pages/invitation-context.d.ts +31 -0
  98. package/dist/pages/invitation-context.d.ts.map +1 -0
  99. package/dist/pages/invitation-context.js +50 -0
  100. package/dist/pages/invitation-context.js.map +1 -0
  101. package/dist/pages/login-page.d.ts +2 -0
  102. package/dist/pages/login-page.d.ts.map +1 -0
  103. package/dist/pages/login-page.js +214 -0
  104. package/dist/pages/login-page.js.map +1 -0
  105. package/dist/pages/logout-page.d.ts +2 -0
  106. package/dist/pages/logout-page.d.ts.map +1 -0
  107. package/dist/pages/logout-page.js +45 -0
  108. package/dist/pages/logout-page.js.map +1 -0
  109. package/dist/pages/password-reset-page.d.ts +2 -0
  110. package/dist/pages/password-reset-page.d.ts.map +1 -0
  111. package/dist/pages/password-reset-page.js +265 -0
  112. package/dist/pages/password-reset-page.js.map +1 -0
  113. package/dist/pages/signup-page.d.ts +2 -0
  114. package/dist/pages/signup-page.d.ts.map +1 -0
  115. package/dist/pages/signup-page.js +384 -0
  116. package/dist/pages/signup-page.js.map +1 -0
  117. package/dist/pages/verify-email-page.d.ts +2 -0
  118. package/dist/pages/verify-email-page.d.ts.map +1 -0
  119. package/dist/pages/verify-email-page.js +57 -0
  120. package/dist/pages/verify-email-page.js.map +1 -0
  121. package/dist/pages/workspace-onboarding-page.d.ts +2 -0
  122. package/dist/pages/workspace-onboarding-page.d.ts.map +1 -0
  123. package/dist/pages/workspace-onboarding-page.js +294 -0
  124. package/dist/pages/workspace-onboarding-page.js.map +1 -0
  125. package/dist/routes/index.d.ts +10 -0
  126. package/dist/routes/index.d.ts.map +1 -0
  127. package/dist/routes/index.js +29 -0
  128. package/dist/routes/index.js.map +1 -0
  129. package/dist/routes/login.d.ts +5 -0
  130. package/dist/routes/login.d.ts.map +1 -0
  131. package/dist/routes/login.js +11 -0
  132. package/dist/routes/login.js.map +1 -0
  133. package/dist/routes/logout.d.ts +6 -0
  134. package/dist/routes/logout.d.ts.map +1 -0
  135. package/dist/routes/logout.js +7 -0
  136. package/dist/routes/logout.js.map +1 -0
  137. package/dist/routes/reset.d.ts +5 -0
  138. package/dist/routes/reset.d.ts.map +1 -0
  139. package/dist/routes/reset.js +11 -0
  140. package/dist/routes/reset.js.map +1 -0
  141. package/dist/routes/signup.d.ts +5 -0
  142. package/dist/routes/signup.d.ts.map +1 -0
  143. package/dist/routes/signup.js +11 -0
  144. package/dist/routes/signup.js.map +1 -0
  145. package/dist/routes/verify-email.d.ts +5 -0
  146. package/dist/routes/verify-email.d.ts.map +1 -0
  147. package/dist/routes/verify-email.js +11 -0
  148. package/dist/routes/verify-email.js.map +1 -0
  149. package/dist/routes/workspace-onboarding.d.ts +13 -0
  150. package/dist/routes/workspace-onboarding.d.ts.map +1 -0
  151. package/dist/routes/workspace-onboarding.js +18 -0
  152. package/dist/routes/workspace-onboarding.js.map +1 -0
  153. package/dist/state/auth.d.ts +10 -0
  154. package/dist/state/auth.d.ts.map +1 -0
  155. package/dist/state/auth.js +9 -0
  156. package/dist/state/auth.js.map +1 -0
  157. package/dist/state/last-workspace.d.ts +2 -0
  158. package/dist/state/last-workspace.d.ts.map +1 -0
  159. package/dist/state/last-workspace.js +3 -0
  160. package/dist/state/last-workspace.js.map +1 -0
  161. package/dist/tsconfig.test.tsbuildinfo +1 -0
  162. package/package.json +116 -0
  163. package/src/components/auth-guard.tsx +58 -0
  164. package/src/components/auth-provider.test.tsx +164 -0
  165. package/src/components/auth-provider.tsx +24 -0
  166. package/src/components/auth-shell.tsx +53 -0
  167. package/src/components/redirect-target.test.ts +60 -0
  168. package/src/components/redirect-target.ts +21 -0
  169. package/src/components/workspace-crumb.tsx +2 -0
  170. package/src/components/workspace-switcher.stories.tsx +169 -0
  171. package/src/components/workspace-switcher.tsx +76 -0
  172. package/src/feature.ts +18 -0
  173. package/src/hooks/api/login-auth.test.ts +45 -0
  174. package/src/hooks/api/login-auth.ts +41 -0
  175. package/src/hooks/api/logout-auth.ts +26 -0
  176. package/src/hooks/api/password-reset-auth.test.ts +68 -0
  177. package/src/hooks/api/password-reset-auth.ts +51 -0
  178. package/src/hooks/api/refresh-auth.test.ts +37 -0
  179. package/src/hooks/api/refresh-auth.ts +5 -0
  180. package/src/hooks/api/signup-auth.ts +11 -0
  181. package/src/hooks/api/verify-email-auth.ts +55 -0
  182. package/src/hooks/api/workspace-auth.test.ts +40 -0
  183. package/src/hooks/api/workspace-auth.ts +25 -0
  184. package/src/hooks/index.ts +13 -0
  185. package/src/hooks/use-active-workspace.ts +1 -0
  186. package/src/hooks/use-auth-state.ts +1 -0
  187. package/src/index.ts +18 -0
  188. package/src/pages/email-verification-resend-model.test.ts +52 -0
  189. package/src/pages/email-verification-resend-model.ts +21 -0
  190. package/src/pages/form-errors.test.ts +114 -0
  191. package/src/pages/form-errors.ts +45 -0
  192. package/src/pages/form-utils.test.ts +37 -0
  193. package/src/pages/form-utils.ts +29 -0
  194. package/src/pages/invitation-context.ts +57 -0
  195. package/src/pages/login-page.test.tsx +128 -0
  196. package/src/pages/login-page.tsx +184 -0
  197. package/src/pages/logout-page.tsx +33 -0
  198. package/src/pages/password-reset-page.test.tsx +109 -0
  199. package/src/pages/password-reset-page.tsx +222 -0
  200. package/src/pages/signup-page.test.tsx +289 -0
  201. package/src/pages/signup-page.tsx +354 -0
  202. package/src/pages/verify-email-page.test.tsx +82 -0
  203. package/src/pages/verify-email-page.tsx +48 -0
  204. package/src/pages/workspace-onboarding-page.test.tsx +161 -0
  205. package/src/pages/workspace-onboarding-page.tsx +213 -0
  206. package/src/routes/index.tsx +18 -0
  207. package/src/routes/login.tsx +11 -0
  208. package/src/routes/logout.tsx +4 -0
  209. package/src/routes/reset.tsx +11 -0
  210. package/src/routes/signup.tsx +11 -0
  211. package/src/routes/verify-email.tsx +11 -0
  212. package/src/routes/workspace-onboarding.tsx +14 -0
  213. package/src/state/auth.ts +18 -0
  214. package/src/state/last-workspace.test.ts +63 -0
  215. package/src/state/last-workspace.ts +5 -0
  216. package/test/factories/user.ts +17 -0
  217. package/test/pages.tsx +83 -0
  218. package/test/setup.ts +3 -0
  219. package/test/utils.ts +11 -0
  220. package/tsconfig.build.json +10 -0
  221. package/tsconfig.build.tsbuildinfo +1 -0
  222. package/tsconfig.json +3 -0
  223. package/tsconfig.test.json +8 -0
  224. package/vercel.json +8 -0
  225. package/vitest.config.ts +71 -0
@@ -0,0 +1 @@
1
+ export {default} from '../../.storybook/main.ts';
@@ -0,0 +1,7 @@
1
+ @import "@shipfox/react-ui/index.css";
2
+
3
+ /* The import supplies design-system tokens and base styles. Tailwind v4 only emits
4
+ utilities from scanned source, so include auth stories plus react-ui components
5
+ rendered by the switcher. */
6
+ @source "../src";
7
+ @source "../../../shared/react/ui/src";
@@ -0,0 +1,56 @@
1
+ import './preview.css';
2
+ import {ThemeProvider} from '@shipfox/react-ui/theme';
3
+ import type {Decorator, Preview} from '@storybook/react';
4
+
5
+ if (typeof document !== 'undefined' && document.fonts) {
6
+ void Promise.all([
7
+ document.fonts.load("16px 'Inter'"),
8
+ document.fonts.load("italic 16px 'Inter'"),
9
+ document.fonts.load("16px 'Commit Mono'"),
10
+ document.fonts.load("bold 16px 'Commit Mono'"),
11
+ ]);
12
+ }
13
+
14
+ const withTheme: Decorator = (Story, context) => {
15
+ const theme = context.globals.theme;
16
+ return (
17
+ <ThemeProvider key={theme} defaultTheme={theme} storageKey={`shipfox-theme-${theme}`}>
18
+ <Story />
19
+ </ThemeProvider>
20
+ );
21
+ };
22
+
23
+ const preview: Preview = {
24
+ decorators: [withTheme],
25
+ parameters: {
26
+ argos: {
27
+ // Dual-theme coverage lives in @shipfox/react-ui (the theming source of truth);
28
+ // feature packages capture only the primary dark theme to limit Argos spend.
29
+ modes: {
30
+ dark: {theme: 'dark'},
31
+ },
32
+ fitToContent: false,
33
+ },
34
+ options: {
35
+ storySort: {method: 'alphabetical'},
36
+ },
37
+ },
38
+ globalTypes: {
39
+ theme: {
40
+ name: 'Theme',
41
+ description: 'Global theme for components',
42
+ defaultValue: 'dark',
43
+ toolbar: {
44
+ icon: 'sun',
45
+ items: [
46
+ {value: 'light', icon: 'sun', title: 'Light'},
47
+ {value: 'dark', icon: 'moon', title: 'Dark'},
48
+ {value: 'system', icon: 'info', title: 'System'},
49
+ ],
50
+ showName: true,
51
+ },
52
+ },
53
+ },
54
+ };
55
+
56
+ export default preview;
@@ -0,0 +1,2 @@
1
+ $ shipfox-swc
2
+ Successfully compiled: 38 files with swc (37.01ms)
@@ -0,0 +1 @@
1
+ $ shipfox-tsc-emit
@@ -0,0 +1 @@
1
+ $ shipfox-tsc-check
package/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # @shipfox/client-auth
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3d064b8: Publishes the client runtime closure with shell, feature, route, Vite, and testing contracts.
8
+ - 6bc2e45: Adds the composable upstream client shell, feature catalog, and route manifests for every client feature.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [3d064b8]
13
+ - Updated dependencies [6bc2e45]
14
+ - @shipfox/client-api@0.2.0
15
+ - @shipfox/client-shell@0.2.0
16
+ - @shipfox/client-ui@0.2.0
17
+
18
+ ## 0.0.5
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [c18d624]
23
+ - @shipfox/react-ui@0.3.1
24
+ - @shipfox/client-shell@0.0.2
25
+ - @shipfox/client-ui@0.1.2
26
+
27
+ ## 0.0.4
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [1b0d344]
32
+ - @shipfox/api-auth-dto@2.0.0
33
+ - @shipfox/api-workspaces-dto@2.0.0
34
+ - @shipfox/client-ui@0.1.1
35
+ - @shipfox/client-api@0.0.1
36
+ - @shipfox/react-ui@0.3.0
37
+
38
+ ## 0.0.3
39
+
40
+ ### Patch Changes
41
+
42
+ - 7790355: Capture the `@shipfox/client-auth` workspace switcher Storybook stories as Argos visual snapshots. Vitest now runs a browser `storybook` project that screenshots the switcher states in light and dark and uploads them to the `client-auth` Argos build in CI.
43
+ - 63bcac8: Moves workspace setup gating into route hooks so VCS onboarding and first project creation resolve before protected workspace content renders.
44
+ - 27770eb: Tightens signup, workspace, and project display-name validation with shared trimming, control and format-character rejection, length limits, and contextual client form errors.
45
+ - Updated dependencies [43d7996]
46
+ - Updated dependencies [14e0bea]
47
+ - Updated dependencies [d02c5fd]
48
+ - Updated dependencies [9018f0b]
49
+ - Updated dependencies [7fdfd72]
50
+ - Updated dependencies [c0a883c]
51
+ - Updated dependencies [72ce351]
52
+ - Updated dependencies [2a3193f]
53
+ - Updated dependencies [f104ff2]
54
+ - Updated dependencies [7341569]
55
+ - Updated dependencies [e250c4c]
56
+ - Updated dependencies [e4c6abf]
57
+ - Updated dependencies [5d0676a]
58
+ - Updated dependencies [3afb7e3]
59
+ - Updated dependencies [a35c2dc]
60
+ - Updated dependencies [58f7aef]
61
+ - Updated dependencies [5264a22]
62
+ - Updated dependencies [9674879]
63
+ - Updated dependencies [225c9a5]
64
+ - Updated dependencies [24f131b]
65
+ - Updated dependencies [bb2a7bc]
66
+ - Updated dependencies [5eb06d0]
67
+ - Updated dependencies [4e13e5f]
68
+ - Updated dependencies [e92150d]
69
+ - Updated dependencies [8037501]
70
+ - Updated dependencies [0fb6018]
71
+ - Updated dependencies [c27a1ed]
72
+ - Updated dependencies [362b3eb]
73
+ - Updated dependencies [b8e49ff]
74
+ - Updated dependencies [8037501]
75
+ - Updated dependencies [6c0da64]
76
+ - Updated dependencies [07f8ff8]
77
+ - Updated dependencies [e457582]
78
+ - Updated dependencies [8b5c905]
79
+ - Updated dependencies [f849131]
80
+ - Updated dependencies [94bdcc5]
81
+ - Updated dependencies [a34c8ea]
82
+ - Updated dependencies [27770eb]
83
+ - Updated dependencies [8ac4bf4]
84
+ - Updated dependencies [6181819]
85
+ - Updated dependencies [3a0be6b]
86
+ - Updated dependencies [d42baf4]
87
+ - Updated dependencies [8037501]
88
+ - Updated dependencies [54bb8a3]
89
+ - Updated dependencies [f711e18]
90
+ - @shipfox/react-ui@0.3.0
91
+ - @shipfox/api-auth-dto@0.1.0
92
+ - @shipfox/api-workspaces-dto@0.1.0
93
+ - @shipfox/client-api@0.0.1
94
+ - @shipfox/client-ui@0.1.0
95
+
96
+ ## 0.0.2
97
+
98
+ ### Patch Changes
99
+
100
+ - Updated dependencies [5c1e777]
101
+ - @shipfox/react-ui@0.2.0
102
+
103
+ ## 0.0.1
104
+
105
+ ### Patch Changes
106
+
107
+ - Updated dependencies [2311e15]
108
+ - @shipfox/react-ui@0.1.1
109
+ - @shipfox/api-auth-dto@0.0.0
110
+ - @shipfox/api-workspaces-dto@0.0.0
111
+ - @shipfox/client-api@0.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Shipfox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ import { type PropsWithChildren } from 'react';
2
+ export declare function AuthGuard({ children }: PropsWithChildren): string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | null | undefined;
3
+ export declare function GuestGuard({ children }: PropsWithChildren): string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | null | undefined;
4
+ export declare function WorkspaceGuard({ children }: PropsWithChildren): string | number | bigint | boolean | import("react").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | null | undefined> | null | undefined;
5
+ //# sourceMappingURL=auth-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-guard.d.ts","sourceRoot":"","sources":["../../src/components/auth-guard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,iBAAiB,EAAY,MAAM,OAAO,CAAC;AAKxD,wBAAgB,SAAS,CAAC,EAAC,QAAQ,EAAC,EAAE,iBAAiB,+VAYtD;AAED,wBAAgB,UAAU,CAAC,EAAC,QAAQ,EAAC,EAAE,iBAAiB,+VA0BvD;AAED,wBAAgB,cAAc,CAAC,EAAC,QAAQ,EAAC,EAAE,iBAAiB,+VAQ3D"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { FullPageLoader } from '@shipfox/react-ui/loader';
3
+ import { Navigate, useRouter, useSearch } from '@tanstack/react-router';
4
+ import { useEffect } from 'react';
5
+ import { useAuthState } from '#hooks/use-auth-state.js';
6
+ import { WorkspaceOnboardingPage } from '#pages/workspace-onboarding-page.js';
7
+ import { sanitizeRedirectPath } from './redirect-target.js';
8
+ export function AuthGuard({ children }) {
9
+ const auth = useAuthState();
10
+ if (auth.isLoading) {
11
+ return /*#__PURE__*/ _jsx(FullPageLoader, {});
12
+ }
13
+ if (!auth.isAuthenticated) {
14
+ return /*#__PURE__*/ _jsx(Navigate, {
15
+ to: "/auth/login",
16
+ replace: true
17
+ });
18
+ }
19
+ return children;
20
+ }
21
+ export function GuestGuard({ children }) {
22
+ const auth = useAuthState();
23
+ const search = useSearch({
24
+ strict: false
25
+ });
26
+ const router = useRouter();
27
+ const target = sanitizeRedirectPath(search.redirect);
28
+ useEffect(()=>{
29
+ if (auth.isAuthenticated && target !== undefined) {
30
+ // Bypass the typed route matcher — `target` is an arbitrary same-origin
31
+ // path resolved at runtime, so we let the URL change drive route resolution.
32
+ router.history.replace(target);
33
+ }
34
+ }, [
35
+ auth.isAuthenticated,
36
+ target,
37
+ router
38
+ ]);
39
+ if (auth.isLoading) {
40
+ return /*#__PURE__*/ _jsx(FullPageLoader, {});
41
+ }
42
+ if (auth.isAuthenticated) {
43
+ if (target !== undefined) {
44
+ return /*#__PURE__*/ _jsx(FullPageLoader, {});
45
+ }
46
+ return /*#__PURE__*/ _jsx(Navigate, {
47
+ to: "/",
48
+ replace: true
49
+ });
50
+ }
51
+ return children;
52
+ }
53
+ export function WorkspaceGuard({ children }) {
54
+ const auth = useAuthState();
55
+ if (auth.isAuthenticated && !auth.hasWorkspace) {
56
+ return /*#__PURE__*/ _jsx(WorkspaceOnboardingPage, {});
57
+ }
58
+ return children;
59
+ }
60
+
61
+ //# sourceMappingURL=auth-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/auth-guard.tsx"],"sourcesContent":["import {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Navigate, useRouter, useSearch} from '@tanstack/react-router';\nimport {type PropsWithChildren, useEffect} from 'react';\nimport {useAuthState} from '#hooks/use-auth-state.js';\nimport {WorkspaceOnboardingPage} from '#pages/workspace-onboarding-page.js';\nimport {sanitizeRedirectPath} from './redirect-target.js';\n\nexport function AuthGuard({children}: PropsWithChildren) {\n const auth = useAuthState();\n\n if (auth.isLoading) {\n return <FullPageLoader />;\n }\n\n if (!auth.isAuthenticated) {\n return <Navigate to=\"/auth/login\" replace />;\n }\n\n return children;\n}\n\nexport function GuestGuard({children}: PropsWithChildren) {\n const auth = useAuthState();\n const search = useSearch({strict: false}) as {redirect?: unknown};\n const router = useRouter();\n const target = sanitizeRedirectPath(search.redirect);\n\n useEffect(() => {\n if (auth.isAuthenticated && target !== undefined) {\n // Bypass the typed route matcher — `target` is an arbitrary same-origin\n // path resolved at runtime, so we let the URL change drive route resolution.\n router.history.replace(target);\n }\n }, [auth.isAuthenticated, target, router]);\n\n if (auth.isLoading) {\n return <FullPageLoader />;\n }\n\n if (auth.isAuthenticated) {\n if (target !== undefined) {\n return <FullPageLoader />;\n }\n return <Navigate to=\"/\" replace />;\n }\n\n return children;\n}\n\nexport function WorkspaceGuard({children}: PropsWithChildren) {\n const auth = useAuthState();\n\n if (auth.isAuthenticated && !auth.hasWorkspace) {\n return <WorkspaceOnboardingPage />;\n }\n\n return children;\n}\n"],"names":["FullPageLoader","Navigate","useRouter","useSearch","useEffect","useAuthState","WorkspaceOnboardingPage","sanitizeRedirectPath","AuthGuard","children","auth","isLoading","isAuthenticated","to","replace","GuestGuard","search","strict","router","target","redirect","undefined","history","WorkspaceGuard","hasWorkspace"],"mappings":";AAAA,SAAQA,cAAc,QAAO,2BAA2B;AACxD,SAAQC,QAAQ,EAAEC,SAAS,EAAEC,SAAS,QAAO,yBAAyB;AACtE,SAAgCC,SAAS,QAAO,QAAQ;AACxD,SAAQC,YAAY,QAAO,2BAA2B;AACtD,SAAQC,uBAAuB,QAAO,sCAAsC;AAC5E,SAAQC,oBAAoB,QAAO,uBAAuB;AAE1D,OAAO,SAASC,UAAU,EAACC,QAAQ,EAAoB;IACrD,MAAMC,OAAOL;IAEb,IAAIK,KAAKC,SAAS,EAAE;QAClB,qBAAO,KAACX;IACV;IAEA,IAAI,CAACU,KAAKE,eAAe,EAAE;QACzB,qBAAO,KAACX;YAASY,IAAG;YAAcC,OAAO;;IAC3C;IAEA,OAAOL;AACT;AAEA,OAAO,SAASM,WAAW,EAACN,QAAQ,EAAoB;IACtD,MAAMC,OAAOL;IACb,MAAMW,SAASb,UAAU;QAACc,QAAQ;IAAK;IACvC,MAAMC,SAAShB;IACf,MAAMiB,SAASZ,qBAAqBS,OAAOI,QAAQ;IAEnDhB,UAAU;QACR,IAAIM,KAAKE,eAAe,IAAIO,WAAWE,WAAW;YAChD,wEAAwE;YACxE,6EAA6E;YAC7EH,OAAOI,OAAO,CAACR,OAAO,CAACK;QACzB;IACF,GAAG;QAACT,KAAKE,eAAe;QAAEO;QAAQD;KAAO;IAEzC,IAAIR,KAAKC,SAAS,EAAE;QAClB,qBAAO,KAACX;IACV;IAEA,IAAIU,KAAKE,eAAe,EAAE;QACxB,IAAIO,WAAWE,WAAW;YACxB,qBAAO,KAACrB;QACV;QACA,qBAAO,KAACC;YAASY,IAAG;YAAIC,OAAO;;IACjC;IAEA,OAAOL;AACT;AAEA,OAAO,SAASc,eAAe,EAACd,QAAQ,EAAoB;IAC1D,MAAMC,OAAOL;IAEb,IAAIK,KAAKE,eAAe,IAAI,CAACF,KAAKc,YAAY,EAAE;QAC9C,qBAAO,KAAClB;IACV;IAEA,OAAOG;AACT"}
@@ -0,0 +1,11 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import { type PropsWithChildren } from 'react';
3
+ export interface AuthProviderProps extends PropsWithChildren {
4
+ queryClient?: QueryClient;
5
+ }
6
+ /**
7
+ * Backwards-compatible provider for applications that have not adopted
8
+ * `composeClientApp` yet. The shell owns the runtime behavior in both paths.
9
+ */
10
+ export declare function AuthProvider({ children, queryClient }: AuthProviderProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=auth-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-provider.d.ts","sourceRoot":"","sources":["../../src/components/auth-provider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAsB,MAAM,uBAAuB,CAAC;AAEvE,OAAO,EAAC,KAAK,iBAAiB,EAAW,MAAM,OAAO,CAAC;AAEvD,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAC,QAAQ,EAAE,WAAW,EAAC,EAAE,iBAAiB,+BAUtE"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AuthRuntime } from '@shipfox/client-shell/runtime';
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { Provider as JotaiProvider } from 'jotai';
5
+ import { useState } from 'react';
6
+ /**
7
+ * Backwards-compatible provider for applications that have not adopted
8
+ * `composeClientApp` yet. The shell owns the runtime behavior in both paths.
9
+ */ export function AuthProvider({ children, queryClient }) {
10
+ const [fallbackQueryClient] = useState(()=>new QueryClient());
11
+ return /*#__PURE__*/ _jsx(QueryClientProvider, {
12
+ client: queryClient ?? fallbackQueryClient,
13
+ children: /*#__PURE__*/ _jsx(JotaiProvider, {
14
+ children: /*#__PURE__*/ _jsx(AuthRuntime, {
15
+ children: children
16
+ })
17
+ })
18
+ });
19
+ }
20
+
21
+ //# sourceMappingURL=auth-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/auth-provider.tsx"],"sourcesContent":["import {AuthRuntime} from '@shipfox/client-shell/runtime';\nimport {QueryClient, QueryClientProvider} from '@tanstack/react-query';\nimport {Provider as JotaiProvider} from 'jotai';\nimport {type PropsWithChildren, useState} from 'react';\n\nexport interface AuthProviderProps extends PropsWithChildren {\n queryClient?: QueryClient;\n}\n\n/**\n * Backwards-compatible provider for applications that have not adopted\n * `composeClientApp` yet. The shell owns the runtime behavior in both paths.\n */\nexport function AuthProvider({children, queryClient}: AuthProviderProps) {\n const [fallbackQueryClient] = useState(() => new QueryClient());\n\n return (\n <QueryClientProvider client={queryClient ?? fallbackQueryClient}>\n <JotaiProvider>\n <AuthRuntime>{children}</AuthRuntime>\n </JotaiProvider>\n </QueryClientProvider>\n );\n}\n"],"names":["AuthRuntime","QueryClient","QueryClientProvider","Provider","JotaiProvider","useState","AuthProvider","children","queryClient","fallbackQueryClient","client"],"mappings":";AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,WAAW,EAAEC,mBAAmB,QAAO,wBAAwB;AACvE,SAAQC,YAAYC,aAAa,QAAO,QAAQ;AAChD,SAAgCC,QAAQ,QAAO,QAAQ;AAMvD;;;CAGC,GACD,OAAO,SAASC,aAAa,EAACC,QAAQ,EAAEC,WAAW,EAAoB;IACrE,MAAM,CAACC,oBAAoB,GAAGJ,SAAS,IAAM,IAAIJ;IAEjD,qBACE,KAACC;QAAoBQ,QAAQF,eAAeC;kBAC1C,cAAA,KAACL;sBACC,cAAA,KAACJ;0BAAaO;;;;AAItB"}
@@ -0,0 +1,11 @@
1
+ import type { PropsWithChildren, ReactNode } from 'react';
2
+ interface AuthShellProps {
3
+ title: string;
4
+ description: string;
5
+ children: ReactNode;
6
+ className?: string;
7
+ }
8
+ export declare function AuthShell({ title, description, children, className }: AuthShellProps): import("react").JSX.Element;
9
+ export declare function AuthActions({ children }: PropsWithChildren): import("react").JSX.Element;
10
+ export {};
11
+ //# sourceMappingURL=auth-shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-shell.d.ts","sourceRoot":"","sources":["../../src/components/auth-shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,iBAAiB,EAAE,SAAS,EAAC,MAAM,OAAO,CAAC;AAExD,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,EAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAC,EAAE,cAAc,+BAqClF;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAC,EAAE,iBAAiB,+BAExD"}
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Icon } from '@shipfox/react-ui/icon';
3
+ import { Header, Text } from '@shipfox/react-ui/typography';
4
+ export function AuthShell({ title, description, children, className }) {
5
+ return /*#__PURE__*/ _jsxs("main", {
6
+ className: "relative flex min-h-screen items-center justify-center overflow-hidden bg-background-subtle-base px-24 py-32 max-[520px]:items-start max-[520px]:px-20 max-[520px]:pb-32 max-[520px]:pt-56",
7
+ children: [
8
+ /*#__PURE__*/ _jsx("div", {
9
+ className: "pointer-events-none absolute left-1/2 top-0 h-[380px] w-full max-w-[880px] -translate-x-1/2 -translate-y-[120px] opacity-55 max-[520px]:-translate-y-[170px] max-[520px]:opacity-35",
10
+ "aria-hidden": "true",
11
+ style: {
12
+ maskImage: 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',
13
+ WebkitMaskImage: 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)'
14
+ },
15
+ children: /*#__PURE__*/ _jsx("div", {
16
+ className: "h-full w-full bg-[radial-gradient(circle,rgba(230,62,0,0.48)_1.6px,transparent_1.8px)] bg-[length:44px_44px]"
17
+ })
18
+ }),
19
+ /*#__PURE__*/ _jsxs("section", {
20
+ className: className ?? 'relative flex w-full max-w-[384px] flex-col items-stretch gap-28',
21
+ "aria-labelledby": "auth-title",
22
+ children: [
23
+ /*#__PURE__*/ _jsxs("div", {
24
+ className: "flex flex-col items-center gap-16",
25
+ children: [
26
+ /*#__PURE__*/ _jsx("div", {
27
+ className: "flex size-64 items-center justify-center rounded-12 border border-border-neutral-base bg-background-neutral-base p-10 shadow-button-neutral",
28
+ children: /*#__PURE__*/ _jsx(Icon, {
29
+ name: "shipfox",
30
+ className: "size-42 text-background-highlight-interactive"
31
+ })
32
+ }),
33
+ /*#__PURE__*/ _jsxs("div", {
34
+ className: "flex min-w-[128px] flex-col items-center gap-4 text-center",
35
+ children: [
36
+ /*#__PURE__*/ _jsx(Header, {
37
+ id: "auth-title",
38
+ variant: "h1",
39
+ children: title
40
+ }),
41
+ /*#__PURE__*/ _jsx(Text, {
42
+ size: "sm",
43
+ className: "text-foreground-neutral-subtle",
44
+ children: description
45
+ })
46
+ ]
47
+ })
48
+ ]
49
+ }),
50
+ children
51
+ ]
52
+ })
53
+ ]
54
+ });
55
+ }
56
+ export function AuthActions({ children }) {
57
+ return /*#__PURE__*/ _jsx("div", {
58
+ className: "flex flex-col gap-20",
59
+ children: children
60
+ });
61
+ }
62
+
63
+ //# sourceMappingURL=auth-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/auth-shell.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport type {PropsWithChildren, ReactNode} from 'react';\n\ninterface AuthShellProps {\n title: string;\n description: string;\n children: ReactNode;\n className?: string;\n}\n\nexport function AuthShell({title, description, children, className}: AuthShellProps) {\n return (\n <main className=\"relative flex min-h-screen items-center justify-center overflow-hidden bg-background-subtle-base px-24 py-32 max-[520px]:items-start max-[520px]:px-20 max-[520px]:pb-32 max-[520px]:pt-56\">\n <div\n className=\"pointer-events-none absolute left-1/2 top-0 h-[380px] w-full max-w-[880px] -translate-x-1/2 -translate-y-[120px] opacity-55 max-[520px]:-translate-y-[170px] max-[520px]:opacity-35\"\n aria-hidden=\"true\"\n style={{\n maskImage:\n 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',\n WebkitMaskImage:\n 'radial-gradient(ellipse 85% 100% at 50% 0%, #000 0%, #000 24%, transparent 78%)',\n }}\n >\n <div className=\"h-full w-full bg-[radial-gradient(circle,rgba(230,62,0,0.48)_1.6px,transparent_1.8px)] bg-[length:44px_44px]\" />\n </div>\n\n <section\n className={className ?? 'relative flex w-full max-w-[384px] flex-col items-stretch gap-28'}\n aria-labelledby=\"auth-title\"\n >\n <div className=\"flex flex-col items-center gap-16\">\n <div className=\"flex size-64 items-center justify-center rounded-12 border border-border-neutral-base bg-background-neutral-base p-10 shadow-button-neutral\">\n <Icon name=\"shipfox\" className=\"size-42 text-background-highlight-interactive\" />\n </div>\n <div className=\"flex min-w-[128px] flex-col items-center gap-4 text-center\">\n <Header id=\"auth-title\" variant=\"h1\">\n {title}\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {description}\n </Text>\n </div>\n </div>\n {children}\n </section>\n </main>\n );\n}\n\nexport function AuthActions({children}: PropsWithChildren) {\n return <div className=\"flex flex-col gap-20\">{children}</div>;\n}\n"],"names":["Icon","Header","Text","AuthShell","title","description","children","className","main","div","aria-hidden","style","maskImage","WebkitMaskImage","section","aria-labelledby","name","id","variant","size","AuthActions"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,MAAM,EAAEC,IAAI,QAAO,+BAA+B;AAU1D,OAAO,SAASC,UAAU,EAACC,KAAK,EAAEC,WAAW,EAAEC,QAAQ,EAAEC,SAAS,EAAiB;IACjF,qBACE,MAACC;QAAKD,WAAU;;0BACd,KAACE;gBACCF,WAAU;gBACVG,eAAY;gBACZC,OAAO;oBACLC,WACE;oBACFC,iBACE;gBACJ;0BAEA,cAAA,KAACJ;oBAAIF,WAAU;;;0BAGjB,MAACO;gBACCP,WAAWA,aAAa;gBACxBQ,mBAAgB;;kCAEhB,MAACN;wBAAIF,WAAU;;0CACb,KAACE;gCAAIF,WAAU;0CACb,cAAA,KAACP;oCAAKgB,MAAK;oCAAUT,WAAU;;;0CAEjC,MAACE;gCAAIF,WAAU;;kDACb,KAACN;wCAAOgB,IAAG;wCAAaC,SAAQ;kDAC7Bd;;kDAEH,KAACF;wCAAKiB,MAAK;wCAAKZ,WAAU;kDACvBF;;;;;;oBAINC;;;;;AAIT;AAEA,OAAO,SAASc,YAAY,EAACd,QAAQ,EAAoB;IACvD,qBAAO,KAACG;QAAIF,WAAU;kBAAwBD;;AAChD"}
@@ -0,0 +1,2 @@
1
+ export declare function sanitizeRedirectPath(value: unknown): string | undefined;
2
+ //# sourceMappingURL=redirect-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-target.d.ts","sourceRoot":"","sources":["../../src/components/redirect-target.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAevE"}
@@ -0,0 +1,22 @@
1
+ const SEARCH_OR_HASH_RE = /[?#]/u;
2
+ // Returns the input string only if it is a same-origin internal path safe to
3
+ // navigate to after authentication. We decode before checking so percent-encoded
4
+ // variants like `/%61uth/login` cannot bypass the `/auth/*` rejection.
5
+ export function sanitizeRedirectPath(value) {
6
+ if (typeof value !== 'string') return undefined;
7
+ let decoded;
8
+ try {
9
+ decoded = decodeURIComponent(value);
10
+ } catch {
11
+ return undefined;
12
+ }
13
+ if (!decoded.startsWith('/')) return undefined;
14
+ if (decoded.startsWith('//')) return undefined;
15
+ // Strip search/hash before the /auth check so /auth?token=x and /auth#x
16
+ // cannot bypass the prefix match.
17
+ const pathOnly = decoded.split(SEARCH_OR_HASH_RE, 1)[0] ?? decoded;
18
+ if (pathOnly === '/auth' || pathOnly.startsWith('/auth/')) return undefined;
19
+ return value;
20
+ }
21
+
22
+ //# sourceMappingURL=redirect-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/redirect-target.ts"],"sourcesContent":["const SEARCH_OR_HASH_RE = /[?#]/u;\n\n// Returns the input string only if it is a same-origin internal path safe to\n// navigate to after authentication. We decode before checking so percent-encoded\n// variants like `/%61uth/login` cannot bypass the `/auth/*` rejection.\nexport function sanitizeRedirectPath(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n let decoded: string;\n try {\n decoded = decodeURIComponent(value);\n } catch {\n return undefined;\n }\n if (!decoded.startsWith('/')) return undefined;\n if (decoded.startsWith('//')) return undefined;\n // Strip search/hash before the /auth check so /auth?token=x and /auth#x\n // cannot bypass the prefix match.\n const pathOnly = decoded.split(SEARCH_OR_HASH_RE, 1)[0] ?? decoded;\n if (pathOnly === '/auth' || pathOnly.startsWith('/auth/')) return undefined;\n return value;\n}\n"],"names":["SEARCH_OR_HASH_RE","sanitizeRedirectPath","value","undefined","decoded","decodeURIComponent","startsWith","pathOnly","split"],"mappings":"AAAA,MAAMA,oBAAoB;AAE1B,6EAA6E;AAC7E,iFAAiF;AACjF,uEAAuE;AACvE,OAAO,SAASC,qBAAqBC,KAAc;IACjD,IAAI,OAAOA,UAAU,UAAU,OAAOC;IACtC,IAAIC;IACJ,IAAI;QACFA,UAAUC,mBAAmBH;IAC/B,EAAE,OAAM;QACN,OAAOC;IACT;IACA,IAAI,CAACC,QAAQE,UAAU,CAAC,MAAM,OAAOH;IACrC,IAAIC,QAAQE,UAAU,CAAC,OAAO,OAAOH;IACrC,wEAAwE;IACxE,kCAAkC;IAClC,MAAMI,WAAWH,QAAQI,KAAK,CAACR,mBAAmB,EAAE,CAAC,EAAE,IAAII;IAC3D,IAAIG,aAAa,WAAWA,SAASD,UAAU,CAAC,WAAW,OAAOH;IAClE,OAAOD;AACT"}
@@ -0,0 +1,3 @@
1
+ export type { WorkspaceCrumbProps } from '@shipfox/client-shell/runtime';
2
+ export { WorkspaceCrumb } from '@shipfox/client-shell/runtime';
3
+ //# sourceMappingURL=workspace-crumb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-crumb.d.ts","sourceRoot":"","sources":["../../src/components/workspace-crumb.tsx"],"names":[],"mappings":"AAAA,YAAY,EAAC,mBAAmB,EAAC,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { WorkspaceCrumb } from '@shipfox/client-shell/runtime';
2
+
3
+ //# sourceMappingURL=workspace-crumb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/workspace-crumb.tsx"],"sourcesContent":["export type {WorkspaceCrumbProps} from '@shipfox/client-shell/runtime';\nexport {WorkspaceCrumb} from '@shipfox/client-shell/runtime';\n"],"names":["WorkspaceCrumb"],"mappings":"AACA,SAAQA,cAAc,QAAO,gCAAgC"}
@@ -0,0 +1,6 @@
1
+ export interface WorkspaceSwitcherProps {
2
+ activeWorkspaceId: string | undefined;
3
+ onSelect?: () => void;
4
+ }
5
+ export declare function WorkspaceSwitcher({ activeWorkspaceId, onSelect }: WorkspaceSwitcherProps): import("react").JSX.Element;
6
+ //# sourceMappingURL=workspace-switcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-switcher.d.ts","sourceRoot":"","sources":["../../src/components/workspace-switcher.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAAC,iBAAiB,EAAE,QAAQ,EAAC,EAAE,sBAAsB,+BAuDtF"}
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@shipfox/react-ui/command';
3
+ import { Icon } from '@shipfox/react-ui/icon';
4
+ import { useNavigate } from '@tanstack/react-router';
5
+ import { useSetAtom } from 'jotai';
6
+ import { useAuthState } from '#hooks/use-auth-state.js';
7
+ import { lastWorkspaceIdAtom } from '#state/last-workspace.js';
8
+ export function WorkspaceSwitcher({ activeWorkspaceId, onSelect }) {
9
+ const { workspaces } = useAuthState();
10
+ const navigate = useNavigate();
11
+ const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);
12
+ const handleSelect = (workspaceId)=>{
13
+ try {
14
+ setLastWorkspaceId(workspaceId);
15
+ } catch {
16
+ // localStorage may throw in private browsing or quota-exceeded; persistence is best-effort.
17
+ }
18
+ navigate({
19
+ to: '/workspaces/$wid',
20
+ params: {
21
+ wid: workspaceId
22
+ }
23
+ });
24
+ onSelect?.();
25
+ };
26
+ const handleCreate = ()=>{
27
+ navigate({
28
+ to: '/setup/workspaces/new'
29
+ });
30
+ onSelect?.();
31
+ };
32
+ return /*#__PURE__*/ _jsxs(Command, {
33
+ children: [
34
+ /*#__PURE__*/ _jsx(CommandInput, {
35
+ placeholder: "Search workspaces..."
36
+ }),
37
+ /*#__PURE__*/ _jsxs(CommandList, {
38
+ className: "max-h-none overflow-visible overflow-y-visible p-0",
39
+ children: [
40
+ /*#__PURE__*/ _jsxs("div", {
41
+ className: "max-h-300 overflow-y-auto overflow-x-hidden p-4 scrollbar",
42
+ children: [
43
+ /*#__PURE__*/ _jsx(CommandEmpty, {
44
+ children: "No workspaces found."
45
+ }),
46
+ /*#__PURE__*/ _jsx(CommandGroup, {
47
+ heading: "Workspaces",
48
+ children: workspaces.map((workspace)=>/*#__PURE__*/ _jsxs(CommandItem, {
49
+ value: workspace.id,
50
+ keywords: [
51
+ workspace.name
52
+ ],
53
+ onSelect: ()=>handleSelect(workspace.id),
54
+ children: [
55
+ /*#__PURE__*/ _jsx(Icon, {
56
+ name: "check",
57
+ className: `size-16 mr-8 ${activeWorkspaceId === workspace.id ? 'opacity-100' : 'opacity-0'}`
58
+ }),
59
+ workspace.name
60
+ ]
61
+ }, workspace.id))
62
+ })
63
+ ]
64
+ }),
65
+ /*#__PURE__*/ _jsx(CommandSeparator, {
66
+ alwaysRender: true,
67
+ className: "mx-0"
68
+ }),
69
+ /*#__PURE__*/ _jsx(CommandGroup, {
70
+ forceMount: true,
71
+ className: "p-4",
72
+ children: /*#__PURE__*/ _jsxs(CommandItem, {
73
+ value: "__create",
74
+ onSelect: handleCreate,
75
+ forceMount: true,
76
+ children: [
77
+ /*#__PURE__*/ _jsx(Icon, {
78
+ name: "addLine",
79
+ className: "size-16"
80
+ }),
81
+ "Create workspace"
82
+ ]
83
+ })
84
+ })
85
+ ]
86
+ })
87
+ ]
88
+ });
89
+ }
90
+
91
+ //# sourceMappingURL=workspace-switcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/workspace-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useSetAtom} from 'jotai';\nimport {useAuthState} from '#hooks/use-auth-state.js';\nimport {lastWorkspaceIdAtom} from '#state/last-workspace.js';\n\nexport interface WorkspaceSwitcherProps {\n activeWorkspaceId: string | undefined;\n onSelect?: () => void;\n}\n\nexport function WorkspaceSwitcher({activeWorkspaceId, onSelect}: WorkspaceSwitcherProps) {\n const {workspaces} = useAuthState();\n const navigate = useNavigate();\n const setLastWorkspaceId = useSetAtom(lastWorkspaceIdAtom);\n\n const handleSelect = (workspaceId: string) => {\n try {\n setLastWorkspaceId(workspaceId);\n } catch {\n // localStorage may throw in private browsing or quota-exceeded; persistence is best-effort.\n }\n navigate({to: '/workspaces/$wid', params: {wid: workspaceId}});\n onSelect?.();\n };\n\n const handleCreate = () => {\n navigate({to: '/setup/workspaces/new'});\n onSelect?.();\n };\n\n return (\n <Command>\n <CommandInput placeholder=\"Search workspaces...\" />\n <CommandList className=\"max-h-none overflow-visible overflow-y-visible p-0\">\n <div className=\"max-h-300 overflow-y-auto overflow-x-hidden p-4 scrollbar\">\n <CommandEmpty>No workspaces found.</CommandEmpty>\n <CommandGroup heading=\"Workspaces\">\n {workspaces.map((workspace) => (\n <CommandItem\n key={workspace.id}\n value={workspace.id}\n keywords={[workspace.name]}\n onSelect={() => handleSelect(workspace.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${\n activeWorkspaceId === workspace.id ? 'opacity-100' : 'opacity-0'\n }`}\n />\n {workspace.name}\n </CommandItem>\n ))}\n </CommandGroup>\n </div>\n <CommandSeparator alwaysRender className=\"mx-0\" />\n <CommandGroup forceMount className=\"p-4\">\n <CommandItem value=\"__create\" onSelect={handleCreate} forceMount>\n <Icon name=\"addLine\" className=\"size-16\" />\n Create workspace\n </CommandItem>\n </CommandGroup>\n </CommandList>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useSetAtom","useAuthState","lastWorkspaceIdAtom","WorkspaceSwitcher","activeWorkspaceId","onSelect","workspaces","navigate","setLastWorkspaceId","handleSelect","workspaceId","to","params","wid","handleCreate","placeholder","className","div","heading","map","workspace","value","id","keywords","name","alwaysRender","forceMount"],"mappings":";AAAA,SACEA,OAAO,EACPC,YAAY,EACZC,YAAY,EACZC,YAAY,EACZC,WAAW,EACXC,WAAW,EACXC,gBAAgB,QACX,4BAA4B;AACnC,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,yBAAyB;AACnD,SAAQC,UAAU,QAAO,QAAQ;AACjC,SAAQC,YAAY,QAAO,2BAA2B;AACtD,SAAQC,mBAAmB,QAAO,2BAA2B;AAO7D,OAAO,SAASC,kBAAkB,EAACC,iBAAiB,EAAEC,QAAQ,EAAyB;IACrF,MAAM,EAACC,UAAU,EAAC,GAAGL;IACrB,MAAMM,WAAWR;IACjB,MAAMS,qBAAqBR,WAAWE;IAEtC,MAAMO,eAAe,CAACC;QACpB,IAAI;YACFF,mBAAmBE;QACrB,EAAE,OAAM;QACN,4FAA4F;QAC9F;QACAH,SAAS;YAACI,IAAI;YAAoBC,QAAQ;gBAACC,KAAKH;YAAW;QAAC;QAC5DL;IACF;IAEA,MAAMS,eAAe;QACnBP,SAAS;YAACI,IAAI;QAAuB;QACrCN;IACF;IAEA,qBACE,MAACd;;0BACC,KAACG;gBAAaqB,aAAY;;0BAC1B,MAACnB;gBAAYoB,WAAU;;kCACrB,MAACC;wBAAID,WAAU;;0CACb,KAACxB;0CAAa;;0CACd,KAACC;gCAAayB,SAAQ;0CACnBZ,WAAWa,GAAG,CAAC,CAACC,0BACf,MAACzB;wCAEC0B,OAAOD,UAAUE,EAAE;wCACnBC,UAAU;4CAACH,UAAUI,IAAI;yCAAC;wCAC1BnB,UAAU,IAAMI,aAAaW,UAAUE,EAAE;;0DAEzC,KAACxB;gDACC0B,MAAK;gDACLR,WAAW,CAAC,aAAa,EACvBZ,sBAAsBgB,UAAUE,EAAE,GAAG,gBAAgB,aACrD;;4CAEHF,UAAUI,IAAI;;uCAXVJ,UAAUE,EAAE;;;;kCAgBzB,KAACzB;wBAAiB4B,YAAY;wBAACT,WAAU;;kCACzC,KAACvB;wBAAaiC,UAAU;wBAACV,WAAU;kCACjC,cAAA,MAACrB;4BAAY0B,OAAM;4BAAWhB,UAAUS;4BAAcY,UAAU;;8CAC9D,KAAC5B;oCAAK0B,MAAK;oCAAUR,WAAU;;gCAAY;;;;;;;;AAOvD"}
@@ -0,0 +1,26 @@
1
+ import type { StoryObj } from '@storybook/react';
2
+ import { type Workspace } from '#state/auth.js';
3
+ interface WorkspaceSwitcherStoryProps {
4
+ activeWorkspaceId: string;
5
+ workspaces: Workspace[];
6
+ }
7
+ declare function WorkspaceSwitcherStory({ activeWorkspaceId, workspaces }: WorkspaceSwitcherStoryProps): import("react").JSX.Element;
8
+ declare const meta: {
9
+ title: string;
10
+ component: typeof WorkspaceSwitcherStory;
11
+ parameters: {
12
+ layout: string;
13
+ };
14
+ args: {
15
+ activeWorkspaceId: string;
16
+ workspaces: Workspace[];
17
+ };
18
+ };
19
+ export default meta;
20
+ type Story = StoryObj<typeof meta>;
21
+ export declare const Playground: Story;
22
+ export declare const SingleWithCreate: Story;
23
+ export declare const EmptySearch: Story;
24
+ export declare const ManyOverflow: Story;
25
+ export declare const ManyOverflowScrolled: Story;
26
+ //# sourceMappingURL=workspace-switcher.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-switcher.stories.d.ts","sourceRoot":"","sources":["../../src/components/workspace-switcher.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAYrD,OAAO,EAAgB,KAAK,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAmB7D,UAAU,2BAA2B;IACnC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAUD,iBAAS,sBAAsB,CAAC,EAAC,iBAAiB,EAAE,UAAU,EAAC,EAAE,2BAA2B,+BAkC3F;AAED,QAAA,MAAM,IAAI;;;;;;;;;;CAUqC,CAAC;AAEhD,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AASnC,eAAO,MAAM,UAAU,EAAE,KAIxB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,KAO9B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAazB,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,KAQ1B,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,KAyBlC,CAAC"}