@vertesia/ui 1.5.0-dev.20260910.052912Z → 1.6.0-dev.20260912.110338Z

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 (389) hide show
  1. package/README.md +125 -0
  2. package/lib/boot/branding.d.ts +58 -0
  3. package/lib/boot/branding.d.ts.map +1 -0
  4. package/lib/boot/branding.js +62 -0
  5. package/lib/boot/branding.js.map +1 -0
  6. package/lib/boot/index.d.ts +27 -0
  7. package/lib/boot/index.d.ts.map +1 -0
  8. package/lib/boot/index.js +325 -0
  9. package/lib/boot/index.js.map +1 -0
  10. package/lib/boot/vite.d.ts +63 -0
  11. package/lib/boot/vite.d.ts.map +1 -0
  12. package/lib/boot/vite.js +108 -0
  13. package/lib/boot/vite.js.map +1 -0
  14. package/lib/core/components/Link.js +1 -1
  15. package/lib/core/components/Link.js.map +1 -1
  16. package/lib/core/components/Spinner.js +1 -1
  17. package/lib/core/components/Spinner.js.map +1 -1
  18. package/lib/core/components/TagsInput.js +7 -7
  19. package/lib/core/components/TagsInput.js.map +1 -1
  20. package/lib/core/components/shadcn/badge.js +1 -1
  21. package/lib/core/components/shadcn/badge.js.map +1 -1
  22. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  23. package/lib/core/components/shadcn/button.js +3 -4
  24. package/lib/core/components/shadcn/button.js.map +1 -1
  25. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js +2 -2
  26. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js.map +1 -1
  27. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js +1 -1
  28. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js.map +1 -1
  29. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js +1 -1
  30. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js.map +1 -1
  31. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js +1 -1
  32. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js.map +1 -1
  33. package/lib/core/components/shadcn/filters/filter/dateFilter.js +1 -1
  34. package/lib/core/components/shadcn/filters/filter/dateFilter.js.map +1 -1
  35. package/lib/core/components/shadcn/filters/filter-styles.d.ts +1 -1
  36. package/lib/core/components/shadcn/filters/filter-styles.d.ts.map +1 -1
  37. package/lib/core/components/shadcn/filters/filter-styles.js +4 -4
  38. package/lib/core/components/shadcn/filters/filters.js +1 -1
  39. package/lib/core/components/shadcn/filters/filters.js.map +1 -1
  40. package/lib/core/components/shadcn/label.js +1 -1
  41. package/lib/core/components/shadcn/label.js.map +1 -1
  42. package/lib/core/components/shadcn/overflowTabs.js +2 -2
  43. package/lib/core/components/shadcn/overflowTabs.js.map +1 -1
  44. package/lib/core/components/shadcn/tabs.js +1 -1
  45. package/lib/core/components/shadcn/tabs.js.map +1 -1
  46. package/lib/env/index.d.ts +28 -1
  47. package/lib/env/index.d.ts.map +1 -1
  48. package/lib/env/index.js +51 -3
  49. package/lib/env/index.js.map +1 -1
  50. package/lib/features/activity-doc/ActivityDoc.js +1 -1
  51. package/lib/features/activity-doc/ActivityDoc.js.map +1 -1
  52. package/lib/features/agent/PayloadBuilder.d.ts +4 -0
  53. package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
  54. package/lib/features/agent/PayloadBuilder.js.map +1 -1
  55. package/lib/features/agent/chat/AgentRunFeedback.d.ts +59 -0
  56. package/lib/features/agent/chat/AgentRunFeedback.d.ts.map +1 -0
  57. package/lib/features/agent/chat/AgentRunFeedback.js +286 -0
  58. package/lib/features/agent/chat/AgentRunFeedback.js.map +1 -0
  59. package/lib/features/agent/chat/AskUserWidget.d.ts +1 -0
  60. package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
  61. package/lib/features/agent/chat/AskUserWidget.js +15 -4
  62. package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
  63. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  64. package/lib/features/agent/chat/ModernAgentConversation.js +3 -2
  65. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  66. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
  67. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  68. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +16 -13
  69. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
  71. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  72. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
  73. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  74. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +30 -8
  75. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  76. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
  77. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +2 -0
  78. package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
  79. package/lib/features/agent/chat/index.d.ts +1 -0
  80. package/lib/features/agent/chat/index.d.ts.map +1 -1
  81. package/lib/features/agent/chat/index.js +1 -0
  82. package/lib/features/agent/chat/index.js.map +1 -1
  83. package/lib/features/facets/AgentRunnerFacetsNav.d.ts +8 -0
  84. package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
  85. package/lib/features/facets/AgentRunnerFacetsNav.js +12 -0
  86. package/lib/features/facets/AgentRunnerFacetsNav.js.map +1 -1
  87. package/lib/features/layout/GenericPageNavHeader.js +1 -1
  88. package/lib/features/layout/GenericPageNavHeader.js.map +1 -1
  89. package/lib/features/magic-pdf/AnnotatedImageSlider.js +1 -1
  90. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  91. package/lib/features/magic-pdf/GroundedExtractionView.js +3 -3
  92. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -1
  93. package/lib/features/permissions/UserPermissionsProvider.d.ts +15 -1
  94. package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
  95. package/lib/features/permissions/UserPermissionsProvider.js +20 -12
  96. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  97. package/lib/features/store/objects/DocumentPreviewPanel.js +5 -5
  98. package/lib/features/store/objects/DocumentPreviewPanel.js.map +1 -1
  99. package/lib/features/store/objects/components/DocumentInput.js +1 -1
  100. package/lib/features/store/objects/components/DocumentInput.js.map +1 -1
  101. package/lib/features/store/objects/selection/SelectionActions.js +1 -1
  102. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  103. package/lib/features/store/objects/upload/DocumentUploadModal.js +1 -1
  104. package/lib/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  105. package/lib/features/store/types/TypeRegistry.d.ts +130 -0
  106. package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
  107. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +130 -0
  108. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
  109. package/lib/features/user/UserInfo.js +1 -1
  110. package/lib/features/user/UserInfo.js.map +1 -1
  111. package/lib/i18n/index.d.ts +5 -4
  112. package/lib/i18n/index.d.ts.map +1 -1
  113. package/lib/i18n/index.js +16 -2
  114. package/lib/i18n/index.js.map +1 -1
  115. package/lib/i18n/locales/ar.json +30 -0
  116. package/lib/i18n/locales/de.json +30 -0
  117. package/lib/i18n/locales/en.json +30 -0
  118. package/lib/i18n/locales/es.json +30 -0
  119. package/lib/i18n/locales/fr.json +30 -0
  120. package/lib/i18n/locales/it.json +30 -0
  121. package/lib/i18n/locales/ja.json +30 -0
  122. package/lib/i18n/locales/ko.json +30 -0
  123. package/lib/i18n/locales/pt.json +30 -0
  124. package/lib/i18n/locales/ru.json +30 -0
  125. package/lib/i18n/locales/tr.json +30 -0
  126. package/lib/i18n/locales/zh-TW.json +30 -0
  127. package/lib/i18n/locales/zh.json +30 -0
  128. package/lib/layout/ErrorPage.js +1 -1
  129. package/lib/layout/ErrorPage.js.map +1 -1
  130. package/lib/layout/Navbar.d.ts.map +1 -1
  131. package/lib/layout/Navbar.js +3 -2
  132. package/lib/layout/Navbar.js.map +1 -1
  133. package/lib/router/RouteComponent.d.ts.map +1 -1
  134. package/lib/router/RouteComponent.js +15 -12
  135. package/lib/router/RouteComponent.js.map +1 -1
  136. package/lib/session/UserSession.d.ts.map +1 -1
  137. package/lib/session/UserSession.js +6 -3
  138. package/lib/session/UserSession.js.map +1 -1
  139. package/lib/session/UserSessionProvider.d.ts +2 -1
  140. package/lib/session/UserSessionProvider.d.ts.map +1 -1
  141. package/lib/session/UserSessionProvider.js +10 -1
  142. package/lib/session/UserSessionProvider.js.map +1 -1
  143. package/lib/session/auth/composable.d.ts.map +1 -1
  144. package/lib/session/auth/composable.js +88 -3
  145. package/lib/session/auth/composable.js.map +1 -1
  146. package/lib/session/auth/domainRouting.d.ts +22 -12
  147. package/lib/session/auth/domainRouting.d.ts.map +1 -1
  148. package/lib/session/auth/domainRouting.js +35 -21
  149. package/lib/session/auth/domainRouting.js.map +1 -1
  150. package/lib/session/auth/useAuthState.d.ts +9 -4
  151. package/lib/session/auth/useAuthState.d.ts.map +1 -1
  152. package/lib/session/auth/useAuthState.js +9 -4
  153. package/lib/session/auth/useAuthState.js.map +1 -1
  154. package/lib/session/index.d.ts.map +1 -1
  155. package/lib/session/index.js +3 -1
  156. package/lib/session/index.js.map +1 -1
  157. package/lib/shell/AuthScreenPreview.d.ts +12 -0
  158. package/lib/shell/AuthScreenPreview.d.ts.map +1 -0
  159. package/lib/shell/AuthScreenPreview.js +140 -0
  160. package/lib/shell/AuthScreenPreview.js.map +1 -0
  161. package/lib/shell/BrandedAuthScreens.d.ts +19 -0
  162. package/lib/shell/BrandedAuthScreens.d.ts.map +1 -0
  163. package/lib/shell/BrandedAuthScreens.js +64 -0
  164. package/lib/shell/BrandedAuthScreens.js.map +1 -0
  165. package/lib/shell/SplashScreen.d.ts +13 -2
  166. package/lib/shell/SplashScreen.d.ts.map +1 -1
  167. package/lib/shell/SplashScreen.js +21 -20
  168. package/lib/shell/SplashScreen.js.map +1 -1
  169. package/lib/shell/VertesiaShell.d.ts +15 -4
  170. package/lib/shell/VertesiaShell.d.ts.map +1 -1
  171. package/lib/shell/VertesiaShell.js +8 -3
  172. package/lib/shell/VertesiaShell.js.map +1 -1
  173. package/lib/shell/apps/StandaloneApp.js +5 -1
  174. package/lib/shell/apps/StandaloneApp.js.map +1 -1
  175. package/lib/shell/index.d.ts +6 -0
  176. package/lib/shell/index.d.ts.map +1 -1
  177. package/lib/shell/index.js +5 -0
  178. package/lib/shell/index.js.map +1 -1
  179. package/lib/shell/login/SignInFlow.d.ts +68 -0
  180. package/lib/shell/login/SignInFlow.d.ts.map +1 -0
  181. package/lib/shell/login/SignInFlow.js +108 -0
  182. package/lib/shell/login/SignInFlow.js.map +1 -0
  183. package/lib/shell/login/SignInPageShell.d.ts +13 -0
  184. package/lib/shell/login/SignInPageShell.d.ts.map +1 -0
  185. package/lib/shell/login/SignInPageShell.js +14 -0
  186. package/lib/shell/login/SignInPageShell.js.map +1 -0
  187. package/lib/shell/login/SignInPrimitives.d.ts +3 -3
  188. package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
  189. package/lib/shell/login/SignInPrimitives.js +13 -11
  190. package/lib/shell/login/SignInPrimitives.js.map +1 -1
  191. package/lib/shell/login/SignInRestrictedEnvStep.d.ts +5 -0
  192. package/lib/shell/login/SignInRestrictedEnvStep.d.ts.map +1 -1
  193. package/lib/shell/login/SignInRestrictedEnvStep.js +8 -4
  194. package/lib/shell/login/SignInRestrictedEnvStep.js.map +1 -1
  195. package/lib/shell/login/SigninScreen.d.ts +38 -3
  196. package/lib/shell/login/SigninScreen.d.ts.map +1 -1
  197. package/lib/shell/login/SigninScreen.js +33 -86
  198. package/lib/shell/login/SigninScreen.js.map +1 -1
  199. package/lib/shell/login/SignupForm.d.ts +3 -1
  200. package/lib/shell/login/SignupForm.d.ts.map +1 -1
  201. package/lib/shell/login/SignupForm.js +7 -3
  202. package/lib/shell/login/SignupForm.js.map +1 -1
  203. package/lib/shell/login/signInUtils.d.ts +11 -0
  204. package/lib/shell/login/signInUtils.d.ts.map +1 -1
  205. package/lib/shell/login/signInUtils.js +29 -5
  206. package/lib/shell/login/signInUtils.js.map +1 -1
  207. package/lib/vertesia-ui-boot.js +237 -0
  208. package/lib/vertesia-ui-boot.js.map +1 -0
  209. package/lib/vertesia-ui-core.js +46 -46
  210. package/lib/vertesia-ui-core.js.map +1 -1
  211. package/lib/vertesia-ui-env.js +1 -1
  212. package/lib/vertesia-ui-env.js.map +1 -1
  213. package/lib/vertesia-ui-features.js +222 -208
  214. package/lib/vertesia-ui-features.js.map +1 -1
  215. package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js +5 -0
  216. package/lib/vertesia-ui-i18n.B3NFclwl.chunk.js.map +1 -0
  217. package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js +5 -0
  218. package/lib/vertesia-ui-i18n.BWSrNIxc.chunk.js.map +1 -0
  219. package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js +5 -0
  220. package/lib/vertesia-ui-i18n.BWvWeB1p.chunk.js.map +1 -0
  221. package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js +5 -0
  222. package/lib/vertesia-ui-i18n.BZCXvTC1.chunk.js.map +1 -0
  223. package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js +5 -0
  224. package/lib/vertesia-ui-i18n.BbJMvFZW.chunk.js.map +1 -0
  225. package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js +5 -0
  226. package/lib/vertesia-ui-i18n.CE5ldwpt.chunk.js.map +1 -0
  227. package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js +5 -0
  228. package/lib/vertesia-ui-i18n.CMC0IShY.chunk.js.map +1 -0
  229. package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js +5 -0
  230. package/lib/vertesia-ui-i18n.CkcRqBKU.chunk.js.map +1 -0
  231. package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js +5 -0
  232. package/lib/vertesia-ui-i18n.D-Mq1sw_.chunk.js.map +1 -0
  233. package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js +5 -0
  234. package/lib/vertesia-ui-i18n.DkK68-A-.chunk.js.map +1 -0
  235. package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js +5 -0
  236. package/lib/vertesia-ui-i18n.XE_dcCg6.chunk.js.map +1 -0
  237. package/lib/vertesia-ui-i18n.js +2 -2
  238. package/lib/vertesia-ui-i18n.js.map +1 -1
  239. package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js +5 -0
  240. package/lib/vertesia-ui-i18n.qgNqMPvK.chunk.js.map +1 -0
  241. package/lib/vertesia-ui-layout.js +13 -13
  242. package/lib/vertesia-ui-layout.js.map +1 -1
  243. package/lib/vertesia-ui-rich-text.js +68 -68
  244. package/lib/vertesia-ui-rich-text.js.map +1 -1
  245. package/lib/vertesia-ui-router.js +1 -1
  246. package/lib/vertesia-ui-router.js.map +1 -1
  247. package/lib/vertesia-ui-session.js +1 -1
  248. package/lib/vertesia-ui-session.js.map +1 -1
  249. package/lib/vertesia-ui-shell.js +58 -33
  250. package/lib/vertesia-ui-shell.js.map +1 -1
  251. package/lib/vertesia-ui-widgets.js +94 -94
  252. package/lib/vertesia-ui-widgets.js.map +1 -1
  253. package/lib/widgets/json-view/JSONCode.js +1 -1
  254. package/lib/widgets/json-view/JSONCode.js.map +1 -1
  255. package/lib/widgets/upload/DropZone.d.ts.map +1 -1
  256. package/lib/widgets/upload/DropZone.js +1 -1
  257. package/lib/widgets/upload/DropZone.js.map +1 -1
  258. package/llms.txt +6 -2
  259. package/package.json +44 -35
  260. package/src/__tests__/a11y.test.tsx +25 -0
  261. package/src/__tests__/theme.test.ts +91 -0
  262. package/src/boot/branding.test.ts +72 -0
  263. package/src/boot/branding.ts +108 -0
  264. package/src/boot/index.test.ts +216 -0
  265. package/src/boot/index.ts +356 -0
  266. package/src/boot/vite.test.ts +93 -0
  267. package/src/boot/vite.ts +118 -0
  268. package/src/core/components/Link.tsx +1 -1
  269. package/src/core/components/Spinner.tsx +1 -1
  270. package/src/core/components/TagsInput.tsx +7 -7
  271. package/src/core/components/shadcn/badge.tsx +1 -1
  272. package/src/core/components/shadcn/button.tsx +3 -5
  273. package/src/core/components/shadcn/filters/comboBox/DateCombobox.tsx +2 -2
  274. package/src/core/components/shadcn/filters/comboBox/SelectCombobox.tsx +1 -1
  275. package/src/core/components/shadcn/filters/comboBox/StringListCombobox.tsx +1 -1
  276. package/src/core/components/shadcn/filters/comboBox/TextCombobox.tsx +1 -1
  277. package/src/core/components/shadcn/filters/filter/dateFilter.tsx +1 -1
  278. package/src/core/components/shadcn/filters/filter-styles.ts +4 -4
  279. package/src/core/components/shadcn/filters/filters.tsx +1 -1
  280. package/src/core/components/shadcn/label.tsx +1 -1
  281. package/src/core/components/shadcn/overflowTabs.tsx +2 -2
  282. package/src/core/components/shadcn/tabs.tsx +1 -1
  283. package/src/css/utilities.css +7 -0
  284. package/src/env/index.test.ts +100 -0
  285. package/src/env/index.ts +66 -3
  286. package/src/features/activity-doc/ActivityDoc.tsx +1 -1
  287. package/src/features/agent/PayloadBuilder.tsx +4 -0
  288. package/src/features/agent/chat/AgentRunFeedback.test.tsx +330 -0
  289. package/src/features/agent/chat/AgentRunFeedback.tsx +452 -0
  290. package/src/features/agent/chat/AskUserWidget.test.tsx +24 -2
  291. package/src/features/agent/chat/AskUserWidget.tsx +19 -4
  292. package/src/features/agent/chat/ModernAgentConversation.tsx +46 -42
  293. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +207 -0
  294. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +50 -31
  295. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +4 -0
  296. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +42 -8
  297. package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +2 -0
  298. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +14 -0
  299. package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +0 -5
  300. package/src/features/agent/chat/index.ts +6 -0
  301. package/src/features/facets/AgentRunnerFacetsNav.tsx +26 -0
  302. package/src/features/layout/GenericPageNavHeader.test.tsx +36 -0
  303. package/src/features/layout/GenericPageNavHeader.tsx +1 -1
  304. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +1 -1
  305. package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
  306. package/src/features/permissions/UserPermissionsProvider.test.tsx +50 -1
  307. package/src/features/permissions/UserPermissionsProvider.tsx +82 -44
  308. package/src/features/store/objects/DocumentPreviewPanel.tsx +10 -10
  309. package/src/features/store/objects/components/DocumentInput.tsx +1 -1
  310. package/src/features/store/objects/selection/SelectionActions.tsx +4 -1
  311. package/src/features/store/objects/upload/DocumentUploadModal.tsx +1 -1
  312. package/src/features/user/UserInfo.tsx +1 -1
  313. package/src/i18n/index.tsx +25 -2
  314. package/src/i18n/locales/ar.json +30 -0
  315. package/src/i18n/locales/de.json +30 -0
  316. package/src/i18n/locales/en.json +30 -0
  317. package/src/i18n/locales/es.json +30 -0
  318. package/src/i18n/locales/fr.json +30 -0
  319. package/src/i18n/locales/it.json +30 -0
  320. package/src/i18n/locales/ja.json +30 -0
  321. package/src/i18n/locales/ko.json +30 -0
  322. package/src/i18n/locales/pt.json +30 -0
  323. package/src/i18n/locales/ru.json +30 -0
  324. package/src/i18n/locales/tr.json +30 -0
  325. package/src/i18n/locales/zh-TW.json +30 -0
  326. package/src/i18n/locales/zh.json +30 -0
  327. package/src/layout/ErrorPage.tsx +1 -1
  328. package/src/layout/Navbar.test.tsx +58 -0
  329. package/src/layout/Navbar.tsx +5 -2
  330. package/src/router/RouteComponent.test.tsx +65 -1
  331. package/src/router/RouteComponent.tsx +18 -15
  332. package/src/session/UserSession.test.ts +65 -0
  333. package/src/session/UserSession.ts +6 -4
  334. package/src/session/UserSessionProvider.strict-mode.test.tsx +44 -0
  335. package/src/session/UserSessionProvider.test.ts +62 -1
  336. package/src/session/UserSessionProvider.tsx +9 -1
  337. package/src/session/auth/composable.renewal.test.ts +170 -0
  338. package/src/session/auth/composable.test.ts +23 -1
  339. package/src/session/auth/composable.ts +101 -2
  340. package/src/session/auth/domainRouting.test.ts +35 -65
  341. package/src/session/auth/domainRouting.ts +51 -34
  342. package/src/session/auth/useAuthState.ts +11 -4
  343. package/src/session/index.ts +3 -1
  344. package/src/shell/AuthScreenPreview.test.tsx +44 -0
  345. package/src/shell/AuthScreenPreview.tsx +228 -0
  346. package/src/shell/BrandedAuthScreens.test.tsx +181 -0
  347. package/src/shell/BrandedAuthScreens.tsx +117 -0
  348. package/src/shell/SplashScreen.test.tsx +37 -0
  349. package/src/shell/SplashScreen.tsx +45 -40
  350. package/src/shell/VertesiaShell.tsx +49 -22
  351. package/src/shell/apps/StandaloneApp.tsx +5 -1
  352. package/src/shell/index.tsx +6 -0
  353. package/src/shell/login/SignInFlow.tsx +213 -0
  354. package/src/shell/login/SignInPageShell.tsx +62 -0
  355. package/src/shell/login/SignInPrimitives.tsx +14 -11
  356. package/src/shell/login/SignInRestrictedEnvStep.tsx +19 -7
  357. package/src/shell/login/SigninScreen.test.tsx +104 -0
  358. package/src/shell/login/SigninScreen.tsx +115 -145
  359. package/src/shell/login/SignupForm.tsx +8 -3
  360. package/src/shell/login/signInUtils.ts +30 -5
  361. package/src/widgets/json-view/JSONCode.test.tsx +2 -2
  362. package/src/widgets/json-view/JSONCode.tsx +1 -1
  363. package/src/widgets/markdown/markdown.css +21 -21
  364. package/src/widgets/upload/DropZone.tsx +2 -4
  365. package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js +0 -5
  366. package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js.map +0 -1
  367. package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js +0 -5
  368. package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js.map +0 -1
  369. package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js +0 -5
  370. package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js.map +0 -1
  371. package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js +0 -5
  372. package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js.map +0 -1
  373. package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js +0 -5
  374. package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js.map +0 -1
  375. package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js +0 -5
  376. package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js.map +0 -1
  377. package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js +0 -5
  378. package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js.map +0 -1
  379. package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js +0 -5
  380. package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js.map +0 -1
  381. package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js +0 -5
  382. package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js.map +0 -1
  383. package/lib/vertesia-ui-i18n.DN79y06J.chunk.js +0 -5
  384. package/lib/vertesia-ui-i18n.DN79y06J.chunk.js.map +0 -1
  385. package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js +0 -5
  386. package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js.map +0 -1
  387. package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js +0 -5
  388. package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js.map +0 -1
  389. package/src/session/auth/authState.test.ts +0 -35
@@ -0,0 +1,104 @@
1
+ import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import { Button } from '@vertesia/ui/core';
3
+ import { Env } from '@vertesia/ui/env';
4
+ import { CredentialError } from '@vertesia/ui/session';
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { type SignInScreenViewProps, SigninScreen } from './SigninScreen';
7
+
8
+ const session = {
9
+ isLoading: false,
10
+ user: undefined as { name: string } | undefined,
11
+ authError: undefined as Error | undefined,
12
+ signOut: vi.fn(),
13
+ };
14
+ vi.mock('@vertesia/ui/session', async (importOriginal) => ({
15
+ ...(await importOriginal<typeof import('@vertesia/ui/session')>()),
16
+ useUserSession: () => session,
17
+ useUXTracking: () => ({ trackEvent: vi.fn() }),
18
+ }));
19
+ vi.mock('./signInUtils', async (importOriginal) => ({
20
+ ...(await importOriginal<typeof import('./signInUtils')>()),
21
+ resetSignInState: vi.fn().mockResolvedValue(undefined),
22
+ }));
23
+
24
+ function CustomSignIn({ flow, onUseDifferentAccount, notice }: SignInScreenViewProps) {
25
+ return (
26
+ <main>
27
+ <h1>My application login</h1>
28
+ <p>Mode: {flow.mode}</p>
29
+ <Button onClick={() => flow.onProceedFromEmail('person@example.com')}>Continue with email</Button>
30
+ <Button onClick={onUseDifferentAccount}>Change identity</Button>
31
+ {notice}
32
+ </main>
33
+ );
34
+ }
35
+
36
+ describe('custom sign-in presentation', () => {
37
+ beforeEach(() => {
38
+ Env.init({
39
+ version: '1.0.0',
40
+ isLocalDev: true,
41
+ isDocker: false,
42
+ type: 'development',
43
+ name: 'Test application',
44
+ endpoints: {
45
+ studio: 'https://studio.example.test',
46
+ zeno: 'https://store.example.test',
47
+ sts: 'https://sts.example.test',
48
+ },
49
+ });
50
+ session.isLoading = false;
51
+ session.user = undefined;
52
+ session.authError = undefined;
53
+ session.signOut.mockClear();
54
+ localStorage.clear();
55
+ sessionStorage.clear();
56
+ history.replaceState({}, '', '/app/deep-link');
57
+ });
58
+ afterEach(cleanup);
59
+
60
+ it('replaces the complete page and drives the existing flow while preserving the return path', () => {
61
+ const view = render(<SigninScreen preservePath View={CustomSignIn} />);
62
+ expect(view.container.firstElementChild?.tagName).toBe('MAIN');
63
+ expect(screen.queryByText('Privacy Policy')).toBeNull();
64
+ expect(screen.getByText('Mode: email')).toBeTruthy();
65
+ fireEvent.click(screen.getByRole('button', { name: 'Continue with email' }));
66
+ expect(screen.getByText('Mode: providers')).toBeTruthy();
67
+ expect(window.location.pathname).toBe('/app/deep-link');
68
+ });
69
+
70
+ it('keeps session gating in shared code for custom views', () => {
71
+ session.isLoading = true;
72
+ const view = render(<SigninScreen preservePath View={CustomSignIn} />);
73
+ expect(screen.queryByText('My application login')).toBeNull();
74
+ session.isLoading = false;
75
+ view.rerender(<SigninScreen preservePath View={CustomSignIn} />);
76
+ expect(screen.getByText('My application login')).toBeTruthy();
77
+ session.user = { name: 'Signed-in user' };
78
+ view.rerender(<SigninScreen preservePath View={CustomSignIn} />);
79
+ expect(screen.queryByText('My application login')).toBeNull();
80
+ });
81
+
82
+ it('retains allowed-path and transient-error suppression for custom views', () => {
83
+ const view = render(<SigninScreen allowedPrefix="/app/" View={CustomSignIn} />);
84
+ expect(screen.queryByText('My application login')).toBeNull();
85
+ session.authError = new Error('Transient failure');
86
+ view.rerender(<SigninScreen suppressAuthErrorPrefix="/app/" View={CustomSignIn} />);
87
+ expect(screen.queryByText('My application login')).toBeNull();
88
+ });
89
+
90
+ it('exposes dedicated recovery modes and working identity reset even on a suppressed path', async () => {
91
+ session.authError = new CredentialError('Expired credential', 'https://sts.example.test');
92
+ render(<SigninScreen preservePath suppressAuthErrorPrefix="/app/" View={CustomSignIn} />);
93
+ expect(await screen.findByText('Mode: credentialFailure')).toBeTruthy();
94
+ fireEvent.click(screen.getByRole('button', { name: 'Change identity' }));
95
+ await waitFor(() => expect(session.signOut).toHaveBeenCalledOnce());
96
+ expect(screen.getByText('Mode: email')).toBeTruthy();
97
+ });
98
+
99
+ it('continues to render the default page when no override is supplied', () => {
100
+ render(<SigninScreen preservePath />);
101
+ expect(screen.queryByText('My application login')).toBeNull();
102
+ expect(screen.getByRole('link', { name: /privacy policy/i })).toBeTruthy();
103
+ });
104
+ });
@@ -1,7 +1,6 @@
1
1
  import { REQUESTED_SCOPE_UNAVAILABLE_ERROR_CODE, type SignupData, type SignupPayload } from '@vertesia/common';
2
2
  import { Env } from '@vertesia/ui/env';
3
3
  import { useUITranslation } from '@vertesia/ui/i18n';
4
- import { RegionTag } from '@vertesia/ui/layout';
5
4
  import {
6
5
  AuthenticationServiceError,
7
6
  CredentialError,
@@ -12,29 +11,33 @@ import {
12
11
  useUserSession,
13
12
  useUXTracking,
14
13
  } from '@vertesia/ui/session';
15
- import { useCallback, useEffect, useState } from 'react';
16
- import SignInAuthPending from './SignInAuthPending';
17
- import SignInEmailStep, { type TenantInfo } from './SignInEmailStep';
18
- import SignInProvidersStep from './SignInProvidersStep';
14
+ import { type ComponentType, type ReactNode, useCallback, useEffect } from 'react';
15
+ import { type SignInFlowController, SignInFlowSteps, useSignInFlow } from './SignInFlow';
16
+ import { SignInPageShell } from './SignInPageShell';
19
17
  import SignInRecoveryStep, { type SignInRecoveryKind } from './SignInRecoveryStep';
20
18
  import SignInRestrictedEnvStep from './SignInRestrictedEnvStep';
21
- import SignInReturningStep from './SignInReturningStep';
22
19
  import SignInTenantBlockedStep from './SignInTenantBlockedStep';
23
- import SignInTenantStep from './SignInTenantStep';
24
20
  import SignupForm from './SignupForm';
25
- import {
26
- clearLastSuccessfulLogin,
27
- clearPendingSignin,
28
- isInviteRequiredError,
29
- type LastSuccessfulLogin,
30
- type ProviderId,
31
- readLastSuccessfulLogin,
32
- readPendingSignin,
33
- resetSignInState,
34
- writeLastSuccessfulLogin,
35
- } from './signInUtils';
21
+ import { isInviteRequiredError, readPendingSignin, resetSignInState } from './signInUtils';
36
22
 
37
- interface SigninScreenProps {
23
+ /** Presentation only: the shared screen still owns session gating and recovery transitions. */
24
+ export interface SignInScreenViewProps {
25
+ flow: SignInFlowController<SignInRecoveryMode>;
26
+ authError?: Error;
27
+ /** Retain these shared forms for tenant resolution and provider sign-in; flow alone does not initiate redirects. */
28
+ children: ReactNode;
29
+ notice: ReactNode;
30
+ isNested: boolean;
31
+ lightLogo?: string;
32
+ darkLogo?: string;
33
+ onUseDifferentAccount: () => void;
34
+ onContinueWithSanitizedScope: () => void;
35
+ onRetry: () => void;
36
+ onSignup: (data: SignupData, firebaseToken: string) => void;
37
+ }
38
+
39
+ export interface SigninScreenProps {
40
+ View?: ComponentType<SignInScreenViewProps>;
38
41
  isNested?: boolean;
39
42
  allowedPrefix?: string | string[];
40
43
  lightLogo?: string;
@@ -50,6 +53,7 @@ export function SigninScreen({
50
53
  darkLogo,
51
54
  preservePath,
52
55
  suppressAuthErrorPrefix,
56
+ View,
53
57
  }: SigninScreenProps) {
54
58
  const pathname = typeof window === 'undefined' ? '' : window.location.pathname;
55
59
  const allow = matchesPathPrefix(pathname, allowedPrefix);
@@ -61,6 +65,7 @@ export function SigninScreen({
61
65
  darkLogo={darkLogo}
62
66
  preservePath={preservePath}
63
67
  suppressAuthError={suppressAuthError}
68
+ View={View}
64
69
  />
65
70
  );
66
71
  }
@@ -75,13 +80,12 @@ function matchesPathPrefix(pathname: string, prefix?: string | string[]) {
75
80
  });
76
81
  }
77
82
 
78
- type Mode =
79
- | 'email'
80
- | 'providers'
81
- | 'tenant'
83
+ /**
84
+ * The modes this screen adds on top of {@link SignInCoreMode}: every one is entered from an
85
+ * `authError` the session surfaced, which is why they stay here rather than in the shared flow.
86
+ */
87
+ export type SignInRecoveryMode =
82
88
  | 'blocked'
83
- | 'returning'
84
- | 'pending'
85
89
  | 'signup'
86
90
  | 'restricted'
87
91
  | 'scopeUnavailable'
@@ -106,19 +110,17 @@ function SigninScreenImpl({
106
110
  darkLogo,
107
111
  preservePath,
108
112
  suppressAuthError,
113
+ View,
109
114
  }: SigninScreenProps & { suppressAuthError?: boolean }) {
110
- const { t } = useUITranslation();
111
115
  const { isLoading, user, authError, signOut } = useUserSession();
112
116
  const { trackEvent } = useUXTracking();
113
117
 
114
- const [storedSession, setStoredSession] = useState<LastSuccessfulLogin | null>(() => readLastSuccessfulLogin());
115
- const [mode, setMode] = useState<Mode>(() => {
116
- const s = readLastSuccessfulLogin();
117
- return s ? 'returning' : 'email';
118
+ const flow = useSignInFlow<SignInRecoveryMode>({
119
+ signOut,
120
+ trackEvent,
121
+ user,
118
122
  });
119
- const [email, setEmail] = useState('');
120
- const [tenant, setTenant] = useState<TenantInfo | undefined>(undefined);
121
- const [pendingProvider, setPendingProvider] = useState<ProviderId | null>(null);
123
+ const { mode, setMode, email, setEmail, tenant, setTenant, storedSession, setStoredSession } = flow;
122
124
 
123
125
  const recoveryIdentity =
124
126
  authError instanceof RequestedScopeUnavailableError || authError instanceof NoAccessibleAccountError
@@ -166,68 +168,14 @@ function SigninScreenImpl({
166
168
  if (pending) setEmail(pending.email);
167
169
  setMode('blocked');
168
170
  }
169
- }, [authError]);
170
-
171
- // On successful login, finalize the last-successful-login entry with the user's name.
172
- useEffect(() => {
173
- if (!user) return;
174
- const pending = readPendingSignin();
175
- if (!pending) return;
176
- writeLastSuccessfulLogin({
177
- email: pending.email,
178
- lastProvider: pending.provider,
179
- tenantName: pending.tenantName,
180
- name: user.name || undefined,
181
- });
182
- clearPendingSignin();
183
- }, [user]);
184
-
185
- const onProceedFromEmail = useCallback((e: string, t: TenantInfo | undefined) => {
186
- setEmail(e);
187
- setTenant(t);
188
- setMode(t ? 'tenant' : 'providers');
189
- }, []);
190
-
191
- const onBack = useCallback(() => {
192
- setMode('email');
193
- setTenant(undefined);
194
- }, []);
195
-
196
- const onNotYou = useCallback(() => {
197
- clearLastSuccessfulLogin();
198
- clearPendingSignin();
199
- setStoredSession(null);
200
- setEmail('');
201
- setTenant(undefined);
202
- setMode('email');
203
- void signOut();
204
- }, [signOut]);
205
-
206
- const onProviderClicked = useCallback(
207
- (provider: ProviderId) => {
208
- // Tenant context comes from a resolved tenant or stored tenantName, not the provider.
209
- const hasTenant = !!tenant || !!storedSession?.tenantName;
210
- // Only the pre-existing enterprise_signin event; non-tenant sign-ins emit nothing.
211
- if (hasTenant) trackEvent('enterprise_signin', { provider });
212
- setPendingProvider(provider);
213
- setMode('pending');
214
- // The redirect itself happens in the calling step's startSignIn(); this just shows the pending screen.
215
- },
216
- [trackEvent, storedSession?.tenantName, tenant],
217
- );
171
+ }, [authError, setEmail, setMode]);
218
172
 
219
173
  // "Use a different email" out of the blocked/signup screen. The user reached it
220
174
  // as a valid Firebase user with no Vertesia account, so a partial reset isn't
221
175
  // enough: unless we also clear the persisted records and sign out of Firebase,
222
176
  // the leftover session re-runs the invite check on the next auth change or
223
177
  // reload and lands them back on blocked.
224
- const startOver = useCallback(() => {
225
- setStoredSession(null); // drop the in-memory mirror too — resetSignInState only clears storage
226
- setEmail('');
227
- setTenant(undefined);
228
- setMode('email');
229
- void resetSignInState();
230
- }, []);
178
+ const { startOver } = flow;
231
179
 
232
180
  const useDifferentAccount = useCallback(() => {
233
181
  Env.logger.info('Authentication recovery action selected', {
@@ -238,7 +186,7 @@ function SigninScreenImpl({
238
186
  setTenant(undefined);
239
187
  setMode('email');
240
188
  void resetSignInState().finally(() => signOut());
241
- }, [signOut]);
189
+ }, [setStoredSession, setEmail, setTenant, setMode, signOut]);
242
190
 
243
191
  const continueWithSanitizedScope = useCallback(() => {
244
192
  if (!(authError instanceof RequestedScopeUnavailableError)) return;
@@ -279,9 +227,7 @@ function SigninScreenImpl({
279
227
  if (isLoading || user || shouldHideTransientAuthError) return null;
280
228
 
281
229
  let content: React.ReactNode = null;
282
- if (mode === 'pending' && pendingProvider) {
283
- content = <SignInAuthPending provider={pendingProvider} />;
284
- } else if (mode === 'blocked') {
230
+ if (mode === 'blocked') {
285
231
  content = (
286
232
  <SignInTenantBlockedStep
287
233
  email={email || storedSession?.email || ''}
@@ -336,67 +282,91 @@ function SigninScreenImpl({
336
282
  );
337
283
  } else if (mode === 'signup' && !localStorage.getItem('tenantName')) {
338
284
  content = <SignupForm onSignup={onSignup} goBack={startOver} />;
339
- } else if (mode === 'tenant' && tenant) {
340
- content = (
341
- <SignInTenantStep
342
- email={email}
343
- tenant={tenant}
344
- onBack={onBack}
345
- onProviderClicked={() => onProviderClicked((tenant.provider ?? 'oidc') as ProviderId)}
346
- />
347
- );
348
- } else if (mode === 'providers') {
349
- content = <SignInProvidersStep email={email} onBack={onBack} onProviderClicked={onProviderClicked} />;
350
- } else if (mode === 'returning' && storedSession) {
351
- content = (
352
- <SignInReturningStep session={storedSession} onNotYou={onNotYou} onProviderClicked={onProviderClicked} />
353
- );
354
285
  } else {
355
- content = <SignInEmailStep initialEmail={email} onProceed={onProceedFromEmail} />;
286
+ // Every remaining mode is a core one; an unmatched recovery mode falls through to the
287
+ // email step, exactly as it did when this chain owned all twelve branches.
288
+ content = <SignInFlowSteps flow={flow} />;
289
+ }
290
+
291
+ const notice = authError && !isDedicatedAuthError(authError) && !isInviteRequiredError(authError) && (
292
+ <SignInErrorNotice />
293
+ );
294
+
295
+ if (View) {
296
+ return (
297
+ <View
298
+ flow={flow}
299
+ authError={authError}
300
+ notice={notice}
301
+ isNested={isNested}
302
+ lightLogo={lightLogo}
303
+ darkLogo={darkLogo}
304
+ onUseDifferentAccount={useDifferentAccount}
305
+ onContinueWithSanitizedScope={continueWithSanitizedScope}
306
+ onRetry={retryAuthentication}
307
+ onSignup={onSignup}
308
+ >
309
+ {content}
310
+ </View>
311
+ );
356
312
  }
357
313
 
314
+ return (
315
+ <DefaultSignInScreen isNested={isNested} lightLogo={lightLogo} darkLogo={darkLogo} notice={notice}>
316
+ {content}
317
+ </DefaultSignInScreen>
318
+ );
319
+ }
320
+
321
+ /** The shared Studio sign-in layout; branding only supplies its content and assets. */
322
+ export function DefaultSignInScreen({
323
+ isNested,
324
+ lightLogo,
325
+ darkLogo,
326
+ notice,
327
+ children,
328
+ logoAlt,
329
+ footer,
330
+ }: {
331
+ isNested?: boolean;
332
+ lightLogo?: string;
333
+ darkLogo?: string;
334
+ notice?: ReactNode;
335
+ children: ReactNode;
336
+ logoAlt?: string;
337
+ footer?: string;
338
+ }) {
358
339
  return (
359
340
  <div
360
341
  style={{ zIndex: 999998 }}
361
342
  className={`${isNested ? 'absolute' : 'fixed'} inset-0 overflow-y-auto bg-background`}
362
343
  >
363
- <div className="min-h-full flex flex-col items-center justify-center py-12 px-4">
364
- <div className="flex flex-col items-center w-full">
365
- {(lightLogo || darkLogo) && (
366
- <div className="mb-7">
367
- {lightLogo && <img src={lightLogo} alt="Vertesia" className="h-10 block dark:hidden" />}
368
- {darkLogo && <img src={darkLogo} alt="Vertesia" className="h-10 hidden dark:block" />}
369
- </div>
370
- )}
371
-
372
- {content}
373
-
374
- {authError && !isDedicatedAuthError(authError) && !isInviteRequiredError(authError) && (
375
- <div className="mt-6 max-w-[420px] text-center text-sm text-muted">
376
- <div>
377
- {t('auth.signInError')}
378
- <br />
379
- {t('auth.signInErrorContact')}
380
- <a className="text-info mx-1" href="mailto:support@vertesiahq.com">
381
- support@vertesiahq.com
382
- </a>
383
- {t('auth.signInErrorPersists')}
384
- </div>
385
- </div>
386
- )}
344
+ <SignInPageShell
345
+ lightLogo={lightLogo}
346
+ darkLogo={darkLogo}
347
+ logoAlt={logoAlt}
348
+ footer={footer}
349
+ notice={notice}
350
+ >
351
+ {children}
352
+ </SignInPageShell>
353
+ </div>
354
+ );
355
+ }
387
356
 
388
- <div className="flex items-center gap-5 mt-10 text-xs text-muted">
389
- <a href="https://vertesiahq.com/privacy" className="hover:text-foreground transition">
390
- {t('auth.privacyPolicy')}
391
- </a>
392
- <span className="text-border">·</span>
393
- <a href="https://vertesiahq.com/terms" className="hover:text-foreground transition">
394
- {t('auth.termsOfService')}
395
- </a>
396
- <span className="text-border">·</span>
397
- <RegionTag className="cursor-default" />
398
- </div>
399
- </div>
357
+ /** Shared generic authentication error notice. */
358
+ export function SignInErrorNotice() {
359
+ const { t } = useUITranslation();
360
+ return (
361
+ <div className="mt-6 max-w-[420px] text-center text-sm text-muted">
362
+ <div>
363
+ {t('auth.signInError')}
364
+ <br />
365
+ {t('auth.signInErrorContact')}
366
+ <a className="text-info mx-1" href="mailto:support@vertesiahq.com">
367
+ support@vertesiahq.com
368
+ </a>
369
+ {t('auth.signInErrorPersists')}
400
370
  </div>
401
371
  </div>
402
372
  );
@@ -11,18 +11,19 @@ interface CompanySizeOption {
11
11
  }
12
12
 
13
13
  interface SignupFormProps {
14
+ identity?: Pick<User, 'displayName' | 'email'>;
14
15
  onSignup: (data: SignupData, fbToken: string) => void;
15
16
  goBack: () => void;
16
17
  }
17
18
 
18
- export default function SignupForm({ onSignup, goBack }: SignupFormProps) {
19
+ export default function SignupForm({ onSignup, goBack, identity }: SignupFormProps) {
19
20
  const { t } = useUITranslation();
20
21
  const [accountType, setAccountType] = useState<string | undefined>(undefined);
21
22
  const [companySize, setCompanySize] = useState<CompanySizeOption | undefined>(undefined);
22
23
  const [companyName, setCompanyName] = useState<string | undefined>(undefined);
23
24
  const [companyWebsite, setCompanyWebsite] = useState<string | undefined>(undefined);
24
25
  const [projectMaturity, setProjectMaturity] = useState<string | undefined>(undefined);
25
- const [fbUser, setFbUser] = useState<User | undefined>(undefined);
26
+ const [fbUser, setFbUser] = useState<Pick<User, 'displayName' | 'email'> | undefined>(identity);
26
27
 
27
28
  const [error, setError] = useState<string | undefined>(undefined);
28
29
  const isCompany = accountType === 'company';
@@ -57,13 +58,17 @@ export default function SignupForm({ onSignup, goBack }: SignupFormProps) {
57
58
  ];
58
59
 
59
60
  useEffect(() => {
61
+ if (identity) {
62
+ setFbUser(identity);
63
+ return;
64
+ }
60
65
  const user = getFirebaseAuth().currentUser;
61
66
  if (!user) {
62
67
  console.error('No user found');
63
68
  return;
64
69
  }
65
70
  setFbUser(user);
66
- }, []);
71
+ }, [identity]);
67
72
 
68
73
  const isValid = () => {
69
74
  if (!accountType) {
@@ -82,13 +82,27 @@ export function clearPendingSignin(): void {
82
82
  }
83
83
 
84
84
  /**
85
- * Clears the persisted sign-in records (last-successful-login and pending) and
86
- * signs out of Firebase. Best-effort: sign-out errors (e.g. no active session)
87
- * are swallowed.
85
+ * Promotes the pending sign-in record to the last-successful-login entry, stamping the
86
+ * authenticated user's display name.
87
+ *
88
+ * The returning-user step reads the record this writes, so a host that never calls it never
89
+ * remembers anyone. No-op when no sign-in is pending, which is the normal case for a page load
90
+ * that did not come back from an identity provider.
88
91
  */
89
- export async function resetSignInState(): Promise<void> {
90
- clearLastSuccessfulLogin();
92
+ export function finalizeSuccessfulLogin(name?: string): void {
93
+ const pending = readPendingSignin();
94
+ if (!pending) return;
95
+ writeLastSuccessfulLogin({
96
+ email: pending.email,
97
+ lastProvider: pending.provider,
98
+ tenantName: pending.tenantName,
99
+ name: name || undefined,
100
+ });
91
101
  clearPendingSignin();
102
+ }
103
+
104
+ /** Drops the Firebase session. Best-effort: no active session, or auth not initialized. */
105
+ export async function signOutOfFirebase(): Promise<void> {
92
106
  try {
93
107
  await firebaseSignOut(getFirebaseAuth());
94
108
  } catch {
@@ -96,6 +110,17 @@ export async function resetSignInState(): Promise<void> {
96
110
  }
97
111
  }
98
112
 
113
+ /**
114
+ * Clears the persisted sign-in records (last-successful-login and pending) and
115
+ * signs out of Firebase. Best-effort: sign-out errors (e.g. no active session)
116
+ * are swallowed.
117
+ */
118
+ export async function resetSignInState(): Promise<void> {
119
+ clearLastSuccessfulLogin();
120
+ clearPendingSignin();
121
+ await signOutOfFirebase();
122
+ }
123
+
99
124
  function buildRedirectPath(redirectTo?: string): string {
100
125
  let path = redirectTo || window.location.pathname || '/';
101
126
  if (path[0] !== '/') path = `/${path}`;
@@ -17,8 +17,8 @@ describe('JSONCode', () => {
17
17
  // number
18
18
  expect(html).toContain('<span class="text-attention">42</span>');
19
19
  // literals
20
- expect(html).toContain('<span class="text-primary">true</span>');
21
- expect(html).toContain('<span class="text-primary">null</span>');
20
+ expect(html).toContain('<span class="text-info">true</span>');
21
+ expect(html).toContain('<span class="text-info">null</span>');
22
22
  });
23
23
 
24
24
  it('treats a string with escaped quotes as a single token', () => {
@@ -54,7 +54,7 @@ export function renderJsonLine(line: string) {
54
54
  : stringValue
55
55
  ? 'text-success'
56
56
  : literal
57
- ? 'text-primary'
57
+ ? 'text-info'
58
58
  : numberValue
59
59
  ? 'text-attention'
60
60
  : undefined;