@vertesia/ui 1.5.0-dev.20260910.052912Z → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/lib/core/components/Link.js +1 -1
  2. package/lib/core/components/Link.js.map +1 -1
  3. package/lib/core/components/SelectList.d.ts.map +1 -1
  4. package/lib/core/components/SelectList.js +3 -3
  5. package/lib/core/components/SelectList.js.map +1 -1
  6. package/lib/core/components/SidePanel.d.ts.map +1 -1
  7. package/lib/core/components/SidePanel.js +2 -1
  8. package/lib/core/components/SidePanel.js.map +1 -1
  9. package/lib/core/components/Spinner.js +1 -1
  10. package/lib/core/components/Spinner.js.map +1 -1
  11. package/lib/core/components/TagsInput.js +7 -7
  12. package/lib/core/components/TagsInput.js.map +1 -1
  13. package/lib/core/components/shadcn/badge.js +1 -1
  14. package/lib/core/components/shadcn/badge.js.map +1 -1
  15. package/lib/core/components/shadcn/button.d.ts.map +1 -1
  16. package/lib/core/components/shadcn/button.js +2 -2
  17. package/lib/core/components/shadcn/button.js.map +1 -1
  18. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js +2 -2
  19. package/lib/core/components/shadcn/filters/comboBox/DateCombobox.js.map +1 -1
  20. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js +1 -1
  21. package/lib/core/components/shadcn/filters/comboBox/SelectCombobox.js.map +1 -1
  22. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js +1 -1
  23. package/lib/core/components/shadcn/filters/comboBox/StringListCombobox.js.map +1 -1
  24. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js +1 -1
  25. package/lib/core/components/shadcn/filters/comboBox/TextCombobox.js.map +1 -1
  26. package/lib/core/components/shadcn/filters/filter/dateFilter.js +1 -1
  27. package/lib/core/components/shadcn/filters/filter/dateFilter.js.map +1 -1
  28. package/lib/core/components/shadcn/filters/filters.js +1 -1
  29. package/lib/core/components/shadcn/filters/filters.js.map +1 -1
  30. package/lib/core/components/shadcn/overflowTabs.js +2 -2
  31. package/lib/core/components/shadcn/overflowTabs.js.map +1 -1
  32. package/lib/core/components/shadcn/tabs.js +1 -1
  33. package/lib/core/components/shadcn/tabs.js.map +1 -1
  34. package/lib/features/activity-doc/ActivityDoc.js +1 -1
  35. package/lib/features/activity-doc/ActivityDoc.js.map +1 -1
  36. package/lib/features/agent/chat/AskUserWidget.d.ts +1 -0
  37. package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
  38. package/lib/features/agent/chat/AskUserWidget.js +15 -4
  39. package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
  40. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  41. package/lib/features/agent/chat/ModernAgentConversation.js +51 -17
  42. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  43. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  44. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +5 -3
  45. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  46. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +5 -1
  47. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  48. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +9 -2
  49. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  50. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  51. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +3 -2
  52. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  53. package/lib/features/agent/chat/hooks/useAgentPlans.d.ts +1 -1
  54. package/lib/features/agent/chat/hooks/useAgentPlans.d.ts.map +1 -1
  55. package/lib/features/agent/chat/hooks/useAgentPlans.js +5 -5
  56. package/lib/features/agent/chat/hooks/useAgentPlans.js.map +1 -1
  57. package/lib/features/layout/GenericPageNavHeader.js +1 -1
  58. package/lib/features/layout/GenericPageNavHeader.js.map +1 -1
  59. package/lib/features/magic-pdf/AnnotatedImageSlider.js +1 -1
  60. package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
  61. package/lib/features/magic-pdf/GroundedExtractionView.js +3 -3
  62. package/lib/features/magic-pdf/GroundedExtractionView.js.map +1 -1
  63. package/lib/features/oauth/McpConnectionsButton.d.ts +2 -2
  64. package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
  65. package/lib/features/oauth/McpConnectionsButton.js +9 -7
  66. package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
  67. package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
  68. package/lib/features/oauth/McpConnectionsDialog.js +7 -3
  69. package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
  70. package/lib/features/oauth/useMcpConnections.d.ts +4 -2
  71. package/lib/features/oauth/useMcpConnections.d.ts.map +1 -1
  72. package/lib/features/oauth/useMcpConnections.js +15 -9
  73. package/lib/features/oauth/useMcpConnections.js.map +1 -1
  74. package/lib/features/permissions/UserPermissionsProvider.js +1 -1
  75. package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
  76. package/lib/features/store/objects/DocumentPreviewPanel.js +5 -5
  77. package/lib/features/store/objects/DocumentPreviewPanel.js.map +1 -1
  78. package/lib/features/store/objects/components/DocumentInput.js +1 -1
  79. package/lib/features/store/objects/components/DocumentInput.js.map +1 -1
  80. package/lib/features/store/objects/selection/ObjectsActionContext.d.ts.map +1 -1
  81. package/lib/features/store/objects/selection/ObjectsActionContext.js +2 -0
  82. package/lib/features/store/objects/selection/ObjectsActionContext.js.map +1 -1
  83. package/lib/features/store/objects/selection/SelectionActions.d.ts +4 -2
  84. package/lib/features/store/objects/selection/SelectionActions.d.ts.map +1 -1
  85. package/lib/features/store/objects/selection/SelectionActions.js +6 -3
  86. package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
  87. package/lib/features/store/objects/selection/actions/RunIntakeAction.d.ts +8 -0
  88. package/lib/features/store/objects/selection/actions/RunIntakeAction.d.ts.map +1 -0
  89. package/lib/features/store/objects/selection/actions/RunIntakeAction.js +65 -0
  90. package/lib/features/store/objects/selection/actions/RunIntakeAction.js.map +1 -0
  91. package/lib/features/store/objects/selection/actions/index.d.ts +1 -0
  92. package/lib/features/store/objects/selection/actions/index.d.ts.map +1 -1
  93. package/lib/features/store/objects/selection/actions/index.js +1 -0
  94. package/lib/features/store/objects/selection/actions/index.js.map +1 -1
  95. package/lib/features/store/objects/upload/DocumentUploadModal.js +1 -1
  96. package/lib/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  97. package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
  98. package/lib/features/store/types/ContentObjectTypesTable.js +11 -3
  99. package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
  100. package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -1
  101. package/lib/features/store/types/IntakePolicyEditor.js +14 -14
  102. package/lib/features/store/types/IntakePolicyEditor.js.map +1 -1
  103. package/lib/features/store/types/TypeRegistry.d.ts +275 -150
  104. package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
  105. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +275 -150
  106. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
  107. package/lib/features/user/UserInfo.d.ts +2 -0
  108. package/lib/features/user/UserInfo.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/locales/ar.json +28 -7
  112. package/lib/i18n/locales/de.json +24 -7
  113. package/lib/i18n/locales/en.json +24 -7
  114. package/lib/i18n/locales/es.json +24 -7
  115. package/lib/i18n/locales/fr.json +25 -7
  116. package/lib/i18n/locales/it.json +24 -7
  117. package/lib/i18n/locales/ja.json +24 -7
  118. package/lib/i18n/locales/ko.json +24 -7
  119. package/lib/i18n/locales/pt.json +24 -7
  120. package/lib/i18n/locales/ru.json +26 -7
  121. package/lib/i18n/locales/tr.json +24 -7
  122. package/lib/i18n/locales/zh-TW.json +24 -7
  123. package/lib/i18n/locales/zh.json +24 -7
  124. package/lib/router/RouteComponent.d.ts.map +1 -1
  125. package/lib/router/RouteComponent.js +15 -12
  126. package/lib/router/RouteComponent.js.map +1 -1
  127. package/lib/session/UserSession.d.ts.map +1 -1
  128. package/lib/session/UserSession.js +4 -0
  129. package/lib/session/UserSession.js.map +1 -1
  130. package/lib/shell/SplashScreen.js +1 -1
  131. package/lib/shell/SplashScreen.js.map +1 -1
  132. package/lib/shell/apps/AppInstallationProvider.d.ts +4 -4
  133. package/lib/shell/login/SignInIcons.d.ts.map +1 -1
  134. package/lib/shell/login/SignInIcons.js +2 -1
  135. package/lib/shell/login/SignInIcons.js.map +1 -1
  136. package/lib/shell/login/SignInPasswordStep.d.ts +9 -0
  137. package/lib/shell/login/SignInPasswordStep.d.ts.map +1 -0
  138. package/lib/shell/login/SignInPasswordStep.js +58 -0
  139. package/lib/shell/login/SignInPasswordStep.js.map +1 -0
  140. package/lib/shell/login/SignInPrimitives.d.ts +13 -0
  141. package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
  142. package/lib/shell/login/SignInPrimitives.js +11 -1
  143. package/lib/shell/login/SignInPrimitives.js.map +1 -1
  144. package/lib/shell/login/SignInProvidersStep.d.ts +3 -1
  145. package/lib/shell/login/SignInProvidersStep.d.ts.map +1 -1
  146. package/lib/shell/login/SignInProvidersStep.js +4 -2
  147. package/lib/shell/login/SignInProvidersStep.js.map +1 -1
  148. package/lib/shell/login/SignInReturningStep.d.ts +3 -1
  149. package/lib/shell/login/SignInReturningStep.d.ts.map +1 -1
  150. package/lib/shell/login/SignInReturningStep.js +18 -8
  151. package/lib/shell/login/SignInReturningStep.js.map +1 -1
  152. package/lib/shell/login/SignInTenantStep.d.ts +3 -1
  153. package/lib/shell/login/SignInTenantStep.d.ts.map +1 -1
  154. package/lib/shell/login/SignInTenantStep.js +2 -2
  155. package/lib/shell/login/SignInTenantStep.js.map +1 -1
  156. package/lib/shell/login/SignInWithProviderButton.d.ts +5 -3
  157. package/lib/shell/login/SignInWithProviderButton.d.ts.map +1 -1
  158. package/lib/shell/login/SignInWithProviderButton.js +8 -7
  159. package/lib/shell/login/SignInWithProviderButton.js.map +1 -1
  160. package/lib/shell/login/SigninScreen.d.ts.map +1 -1
  161. package/lib/shell/login/SigninScreen.js +13 -4
  162. package/lib/shell/login/SigninScreen.js.map +1 -1
  163. package/lib/shell/login/signInUtils.d.ts +20 -4
  164. package/lib/shell/login/signInUtils.d.ts.map +1 -1
  165. package/lib/shell/login/signInUtils.js +47 -1
  166. package/lib/shell/login/signInUtils.js.map +1 -1
  167. package/lib/vertesia-ui-core.js +3 -3
  168. package/lib/vertesia-ui-core.js.map +1 -1
  169. package/lib/vertesia-ui-features.js +33 -33
  170. package/lib/vertesia-ui-features.js.map +1 -1
  171. package/lib/vertesia-ui-i18n.BoNE1APG.chunk.js +5 -0
  172. package/lib/vertesia-ui-i18n.BoNE1APG.chunk.js.map +1 -0
  173. package/lib/vertesia-ui-i18n.BoPtiSPb.chunk.js +5 -0
  174. package/lib/vertesia-ui-i18n.BoPtiSPb.chunk.js.map +1 -0
  175. package/lib/vertesia-ui-i18n.C4BBW5v5.chunk.js +5 -0
  176. package/lib/vertesia-ui-i18n.C4BBW5v5.chunk.js.map +1 -0
  177. package/lib/vertesia-ui-i18n.C81wuNkY.chunk.js +5 -0
  178. package/lib/vertesia-ui-i18n.C81wuNkY.chunk.js.map +1 -0
  179. package/lib/vertesia-ui-i18n.C9GipKIH.chunk.js +5 -0
  180. package/lib/vertesia-ui-i18n.C9GipKIH.chunk.js.map +1 -0
  181. package/lib/vertesia-ui-i18n.CWsoS0W9.chunk.js +5 -0
  182. package/lib/vertesia-ui-i18n.CWsoS0W9.chunk.js.map +1 -0
  183. package/lib/vertesia-ui-i18n.Cr9NtzZB.chunk.js +5 -0
  184. package/lib/vertesia-ui-i18n.Cr9NtzZB.chunk.js.map +1 -0
  185. package/lib/vertesia-ui-i18n.DEpkHOD5.chunk.js +5 -0
  186. package/lib/vertesia-ui-i18n.DEpkHOD5.chunk.js.map +1 -0
  187. package/lib/vertesia-ui-i18n.DPXaRKFO.chunk.js +5 -0
  188. package/lib/vertesia-ui-i18n.DPXaRKFO.chunk.js.map +1 -0
  189. package/lib/vertesia-ui-i18n.D_QNm1Xg.chunk.js +5 -0
  190. package/lib/vertesia-ui-i18n.D_QNm1Xg.chunk.js.map +1 -0
  191. package/lib/vertesia-ui-i18n.F26Cc0f7.chunk.js +5 -0
  192. package/lib/vertesia-ui-i18n.F26Cc0f7.chunk.js.map +1 -0
  193. package/lib/vertesia-ui-i18n.YkXIESV_.chunk.js +5 -0
  194. package/lib/vertesia-ui-i18n.YkXIESV_.chunk.js.map +1 -0
  195. package/lib/vertesia-ui-i18n.js +3 -3
  196. package/lib/vertesia-ui-i18n.js.map +1 -1
  197. package/lib/vertesia-ui-router.js +1 -1
  198. package/lib/vertesia-ui-router.js.map +1 -1
  199. package/lib/vertesia-ui-session.js +1 -1
  200. package/lib/vertesia-ui-session.js.map +1 -1
  201. package/lib/vertesia-ui-shell.js +15 -9
  202. package/lib/vertesia-ui-shell.js.map +1 -1
  203. package/lib/vertesia-ui-widgets.js +2 -2
  204. package/lib/vertesia-ui-widgets.js.map +1 -1
  205. package/lib/widgets/json-view/JSONCode.js +1 -1
  206. package/lib/widgets/json-view/JSONCode.js.map +1 -1
  207. package/lib/widgets/upload/DropZone.d.ts.map +1 -1
  208. package/lib/widgets/upload/DropZone.js +1 -1
  209. package/lib/widgets/upload/DropZone.js.map +1 -1
  210. package/lib/widgets/xml-viewer/hooks/useCollapsible.d.ts +1 -1
  211. package/package.json +7 -7
  212. package/src/core/components/Link.tsx +1 -1
  213. package/src/core/components/SelectList.tsx +5 -2
  214. package/src/core/components/SidePanel.tsx +65 -60
  215. package/src/core/components/Spinner.tsx +1 -1
  216. package/src/core/components/TagsInput.tsx +7 -7
  217. package/src/core/components/shadcn/badge.tsx +1 -1
  218. package/src/core/components/shadcn/button.tsx +2 -3
  219. package/src/core/components/shadcn/filters/comboBox/DateCombobox.tsx +2 -2
  220. package/src/core/components/shadcn/filters/comboBox/SelectCombobox.tsx +1 -1
  221. package/src/core/components/shadcn/filters/comboBox/StringListCombobox.tsx +1 -1
  222. package/src/core/components/shadcn/filters/comboBox/TextCombobox.tsx +1 -1
  223. package/src/core/components/shadcn/filters/filter/dateFilter.tsx +1 -1
  224. package/src/core/components/shadcn/filters/filters.tsx +1 -1
  225. package/src/core/components/shadcn/overflowTabs.tsx +2 -2
  226. package/src/core/components/shadcn/tabs.tsx +1 -1
  227. package/src/css/utilities.css +7 -0
  228. package/src/features/activity-doc/ActivityDoc.tsx +1 -1
  229. package/src/features/agent/chat/AskUserWidget.test.tsx +24 -2
  230. package/src/features/agent/chat/AskUserWidget.tsx +19 -4
  231. package/src/features/agent/chat/ModernAgentConversation.test.tsx +150 -1
  232. package/src/features/agent/chat/ModernAgentConversation.tsx +59 -21
  233. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +62 -0
  234. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +8 -7
  235. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +17 -1
  236. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -3
  237. package/src/features/agent/chat/hooks/useAgentPlans.test.ts +52 -0
  238. package/src/features/agent/chat/hooks/useAgentPlans.ts +5 -5
  239. package/src/features/layout/GenericPageNavHeader.test.tsx +36 -0
  240. package/src/features/layout/GenericPageNavHeader.tsx +1 -1
  241. package/src/features/magic-pdf/AnnotatedImageSlider.tsx +1 -1
  242. package/src/features/magic-pdf/GroundedExtractionView.tsx +3 -3
  243. package/src/features/oauth/McpConnectionsButton.test.tsx +56 -0
  244. package/src/features/oauth/McpConnectionsButton.tsx +9 -6
  245. package/src/features/oauth/McpConnectionsDialog.tsx +14 -5
  246. package/src/features/oauth/useMcpConnections.test.ts +10 -0
  247. package/src/features/oauth/useMcpConnections.ts +18 -10
  248. package/src/features/permissions/UserPermissionsProvider.tsx +1 -1
  249. package/src/features/store/objects/DocumentPreviewPanel.tsx +10 -10
  250. package/src/features/store/objects/components/DocumentInput.tsx +1 -1
  251. package/src/features/store/objects/selection/ObjectsActionContext.tsx +2 -0
  252. package/src/features/store/objects/selection/SelectionActions.tsx +13 -6
  253. package/src/features/store/objects/selection/actions/RunIntakeAction.tsx +69 -0
  254. package/src/features/store/objects/selection/actions/index.ts +1 -0
  255. package/src/features/store/objects/upload/DocumentUploadModal.tsx +1 -1
  256. package/src/features/store/types/ContentObjectTypesTable.tsx +20 -3
  257. package/src/features/store/types/IntakePolicyEditor.tsx +33 -28
  258. package/src/features/user/UserInfo.tsx +1 -1
  259. package/src/i18n/locales/ar.json +28 -7
  260. package/src/i18n/locales/de.json +24 -7
  261. package/src/i18n/locales/en.json +24 -7
  262. package/src/i18n/locales/es.json +24 -7
  263. package/src/i18n/locales/fr.json +25 -7
  264. package/src/i18n/locales/it.json +24 -7
  265. package/src/i18n/locales/ja.json +24 -7
  266. package/src/i18n/locales/ko.json +24 -7
  267. package/src/i18n/locales/pt.json +24 -7
  268. package/src/i18n/locales/ru.json +26 -7
  269. package/src/i18n/locales/tr.json +24 -7
  270. package/src/i18n/locales/zh-TW.json +24 -7
  271. package/src/i18n/locales/zh.json +24 -7
  272. package/src/router/RouteComponent.test.tsx +65 -1
  273. package/src/router/RouteComponent.tsx +18 -15
  274. package/src/session/UserSession.test.ts +65 -0
  275. package/src/session/UserSession.ts +4 -0
  276. package/src/shell/SplashScreen.tsx +1 -1
  277. package/src/shell/login/SignInIcons.tsx +2 -1
  278. package/src/shell/login/SignInPasswordRequired.test.tsx +82 -0
  279. package/src/shell/login/SignInPasswordStep.test.tsx +71 -0
  280. package/src/shell/login/SignInPasswordStep.tsx +103 -0
  281. package/src/shell/login/SignInPrimitives.tsx +53 -1
  282. package/src/shell/login/SignInProvidersStep.tsx +8 -4
  283. package/src/shell/login/SignInReturningStep.tsx +20 -8
  284. package/src/shell/login/SignInTenantStep.tsx +6 -2
  285. package/src/shell/login/SignInWithProviderButton.tsx +11 -7
  286. package/src/shell/login/SigninScreen.tsx +32 -3
  287. package/src/shell/login/signInPassword.test.ts +133 -0
  288. package/src/shell/login/signInUtils.ts +60 -7
  289. package/src/widgets/json-view/JSONCode.test.tsx +2 -2
  290. package/src/widgets/json-view/JSONCode.tsx +1 -1
  291. package/src/widgets/upload/DropZone.tsx +2 -4
  292. package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts +0 -7
  293. package/lib/features/store/objects/selection/actions/StartWorkflowButton.d.ts.map +0 -1
  294. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +0 -51
  295. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +0 -1
  296. package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js +0 -5
  297. package/lib/vertesia-ui-i18n.BfN7iOHb.chunk.js.map +0 -1
  298. package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js +0 -5
  299. package/lib/vertesia-ui-i18n.BjH2zEZw.chunk.js.map +0 -1
  300. package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js +0 -5
  301. package/lib/vertesia-ui-i18n.BjSwAZXl.chunk.js.map +0 -1
  302. package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js +0 -5
  303. package/lib/vertesia-ui-i18n.C7PUm5Au.chunk.js.map +0 -1
  304. package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js +0 -5
  305. package/lib/vertesia-ui-i18n.CF_1mUS5.chunk.js.map +0 -1
  306. package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js +0 -5
  307. package/lib/vertesia-ui-i18n.ChfUl3_-.chunk.js.map +0 -1
  308. package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js +0 -5
  309. package/lib/vertesia-ui-i18n.D-Sk-5d1.chunk.js.map +0 -1
  310. package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js +0 -5
  311. package/lib/vertesia-ui-i18n.D4rnu1_S.chunk.js.map +0 -1
  312. package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js +0 -5
  313. package/lib/vertesia-ui-i18n.DDf0-laK.chunk.js.map +0 -1
  314. package/lib/vertesia-ui-i18n.DN79y06J.chunk.js +0 -5
  315. package/lib/vertesia-ui-i18n.DN79y06J.chunk.js.map +0 -1
  316. package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js +0 -5
  317. package/lib/vertesia-ui-i18n.DWLIciYQ.chunk.js.map +0 -1
  318. package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js +0 -5
  319. package/lib/vertesia-ui-i18n.DvF_YaAJ.chunk.js.map +0 -1
  320. package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +0 -104
@@ -0,0 +1,71 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ const mocks = vi.hoisted(() => ({ signInWithPassword: vi.fn() }));
5
+
6
+ vi.mock('@vertesia/ui/i18n', () => ({
7
+ useUITranslation: () => ({ t: (key: string) => key }),
8
+ }));
9
+
10
+ vi.mock('./signInUtils', () => ({ signInWithPassword: mocks.signInWithPassword }));
11
+
12
+ const { default: SignInPasswordStep } = await import('./SignInPasswordStep');
13
+
14
+ const EMAIL = 'claude@reviewers.vertesia.io';
15
+
16
+ function submitWith(password: string) {
17
+ fireEvent.change(screen.getByLabelText('auth.password.label'), { target: { value: password } });
18
+ fireEvent.click(screen.getByRole('button', { name: /auth.password.submit/ }));
19
+ }
20
+
21
+ describe('SignInPasswordStep', () => {
22
+ afterEach(() => {
23
+ mocks.signInWithPassword.mockReset();
24
+ document.body.innerHTML = '';
25
+ });
26
+
27
+ it('asks for a password before calling sign-in', () => {
28
+ render(<SignInPasswordStep email={EMAIL} onBack={vi.fn()} />);
29
+
30
+ fireEvent.click(screen.getByRole('button', { name: /auth.password.submit/ }));
31
+
32
+ expect(screen.getByRole('alert').textContent).toBe('auth.password.required');
33
+ expect(mocks.signInWithPassword).not.toHaveBeenCalled();
34
+ });
35
+
36
+ it('shows the failure and lets the user retry', async () => {
37
+ mocks.signInWithPassword.mockResolvedValue({ ok: false, reason: 'invalid-credentials' });
38
+ render(<SignInPasswordStep email={EMAIL} onBack={vi.fn()} />);
39
+
40
+ submitWith('wrong');
41
+
42
+ await waitFor(() => expect(screen.getByRole('alert').textContent).toBe('auth.password.invalidCredentials'));
43
+ expect(mocks.signInWithPassword).toHaveBeenCalledWith(EMAIL, 'wrong');
44
+ expect((screen.getByRole('button', { name: /auth.password.submit/ }) as HTMLButtonElement).disabled).toBe(
45
+ false,
46
+ );
47
+ });
48
+
49
+ // Success hands off to the session's auth-state listener, which unmounts the screen; until then
50
+ // the button must stay disabled so a second click cannot start a second sign-in.
51
+ it('keeps the submit disabled after a successful sign-in', async () => {
52
+ mocks.signInWithPassword.mockResolvedValue({ ok: true });
53
+ render(<SignInPasswordStep email={EMAIL} onBack={vi.fn()} />);
54
+
55
+ submitWith('secret');
56
+
57
+ await waitFor(() => expect(mocks.signInWithPassword).toHaveBeenCalledOnce());
58
+ const submit = screen.getAllByRole('button').find((b) => (b as HTMLButtonElement).type === 'submit');
59
+ expect((submit as HTMLButtonElement).disabled).toBe(true);
60
+ expect(screen.queryByRole('alert')).toBeNull();
61
+ });
62
+
63
+ it('returns to the email step from the change link', () => {
64
+ const onBack = vi.fn();
65
+ render(<SignInPasswordStep email={EMAIL} onBack={onBack} />);
66
+
67
+ fireEvent.click(screen.getByRole('button', { name: 'auth.change' }));
68
+
69
+ expect(onBack).toHaveBeenCalledOnce();
70
+ });
71
+ });
@@ -0,0 +1,103 @@
1
+ import { Spinner } from '@vertesia/ui/core';
2
+ import { useUITranslation } from '@vertesia/ui/i18n';
3
+ import { ArrowRight } from 'lucide-react';
4
+ import { useEffect, useRef, useState } from 'react';
5
+ import {
6
+ SignInEmailRow,
7
+ SignInPasswordField,
8
+ SignInStepButton,
9
+ SignInStepHeader,
10
+ SignInStepLayout,
11
+ } from './SignInPrimitives';
12
+ import { type PasswordSignInFailure, signInWithPassword } from './signInUtils';
13
+
14
+ interface SignInPasswordStepProps {
15
+ email: string;
16
+ tenantName?: string;
17
+ onBack: () => void;
18
+ }
19
+
20
+ /** Password sign-in for an address whose tenant issues passwords instead of federating an IdP. */
21
+ export default function SignInPasswordStep({ email, tenantName, onBack }: SignInPasswordStepProps) {
22
+ const { t } = useUITranslation();
23
+ const [password, setPassword] = useState('');
24
+ const [failure, setFailure] = useState<PasswordSignInFailure | null>(null);
25
+ const [emptyError, setEmptyError] = useState(false);
26
+ const [loading, setLoading] = useState(false);
27
+ const inputRef = useRef<HTMLInputElement>(null);
28
+
29
+ useEffect(() => {
30
+ inputRef.current?.focus();
31
+ }, []);
32
+
33
+ const submit = async (e: React.FormEvent) => {
34
+ e.preventDefault();
35
+ if (loading) return;
36
+ if (!password) {
37
+ setEmptyError(true);
38
+ return;
39
+ }
40
+ setEmptyError(false);
41
+ setFailure(null);
42
+ setLoading(true);
43
+ const result = await signInWithPassword(email, password);
44
+ if (!result.ok) {
45
+ setFailure(result.reason);
46
+ setLoading(false);
47
+ }
48
+ // On success the spinner stays up: the session's auth-state listener finishes the login and
49
+ // unmounts this screen.
50
+ };
51
+
52
+ // Literal keys, one per failure, so i18next-cli extraction sees every message.
53
+ const failureMessage = (reason: PasswordSignInFailure) => {
54
+ switch (reason) {
55
+ case 'invalid-credentials':
56
+ return t('auth.password.invalidCredentials');
57
+ case 'too-many-attempts':
58
+ return t('auth.password.tooManyAttempts');
59
+ case 'not-password-tenant':
60
+ return t('auth.password.notAvailable');
61
+ case 'failed':
62
+ return t('auth.password.failed');
63
+ }
64
+ };
65
+ const error = emptyError ? t('auth.password.required') : failure ? failureMessage(failure) : undefined;
66
+
67
+ return (
68
+ <SignInStepLayout>
69
+ <SignInStepHeader
70
+ eyebrow={tenantName ?? t('auth.password.eyebrow')}
71
+ title={t('auth.password.title')}
72
+ body={t('auth.password.body')}
73
+ />
74
+
75
+ <SignInEmailRow email={email} actionLabel={t('auth.change')} onAction={onBack} />
76
+
77
+ <form onSubmit={submit} noValidate className={`flex flex-col ${error ? 'gap-2' : 'gap-6'}`}>
78
+ <SignInPasswordField
79
+ inputRef={inputRef}
80
+ label={t('auth.password.label')}
81
+ value={password}
82
+ onChange={(value) => {
83
+ setPassword(value);
84
+ if (emptyError) setEmptyError(false);
85
+ }}
86
+ invalid={!!error}
87
+ error={error}
88
+ />
89
+
90
+ <SignInStepButton type="submit" disabled={loading}>
91
+ {loading ? (
92
+ <Spinner />
93
+ ) : (
94
+ <>
95
+ <span>{t('auth.password.submit')}</span>
96
+ <ArrowRight className="!size-3.5" />
97
+ </>
98
+ )}
99
+ </SignInStepButton>
100
+ </form>
101
+ </SignInStepLayout>
102
+ );
103
+ }
@@ -363,6 +363,11 @@ export function SignInOrDivider({ children }: { children: ReactNode }) {
363
363
  );
364
364
  }
365
365
 
366
+ const SIGNIN_INPUT_CLASS =
367
+ 'h-[42px] px-3.5 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted ' +
368
+ 'outline-none transition focus:border-info focus:ring-4 focus:ring-info/15 ' +
369
+ 'aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/15';
370
+
366
371
  interface SignInEmailFieldProps {
367
372
  inputRef?: Ref<HTMLInputElement>;
368
373
  label: ReactNode;
@@ -393,7 +398,7 @@ export function SignInEmailField({
393
398
  id="vt-login-email"
394
399
  name="vt-login-email"
395
400
  type="email"
396
- className="h-[42px] px-3.5 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted outline-none transition focus:border-info focus:ring-4 focus:ring-info/15 aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/15"
401
+ className={SIGNIN_INPUT_CLASS}
397
402
  placeholder={placeholder}
398
403
  value={value}
399
404
  onChange={(e) => onChange(e.target.value)}
@@ -414,3 +419,50 @@ export function SignInEmailField({
414
419
  </div>
415
420
  );
416
421
  }
422
+
423
+ interface SignInPasswordFieldProps {
424
+ inputRef?: Ref<HTMLInputElement>;
425
+ label: ReactNode;
426
+ value: string;
427
+ onChange: (value: string) => void;
428
+ invalid?: boolean;
429
+ error?: ReactNode;
430
+ }
431
+
432
+ /**
433
+ * Labeled password input with an inline error. Unlike the email field it leaves password managers
434
+ * enabled: a password account's credential is issued once and is expected to be stored in one.
435
+ */
436
+ export function SignInPasswordField({
437
+ inputRef,
438
+ label,
439
+ value,
440
+ onChange,
441
+ invalid = false,
442
+ error,
443
+ }: SignInPasswordFieldProps) {
444
+ return (
445
+ <div className="flex flex-col gap-1.5">
446
+ <label htmlFor="vt-login-password" className="text-xs font-medium text-foreground/80">
447
+ {label}
448
+ </label>
449
+ <input
450
+ ref={inputRef}
451
+ id="vt-login-password"
452
+ name="vt-login-password"
453
+ type="password"
454
+ className={SIGNIN_INPUT_CLASS}
455
+ value={value}
456
+ onChange={(e) => onChange(e.target.value)}
457
+ aria-invalid={invalid}
458
+ aria-describedby={error ? 'vt-login-password-error' : undefined}
459
+ autoComplete="current-password"
460
+ />
461
+ {error && (
462
+ <div id="vt-login-password-error" role="alert" className="text-xs text-destructive">
463
+ {error}
464
+ </div>
465
+ )}
466
+ </div>
467
+ );
468
+ }
@@ -1,27 +1,31 @@
1
1
  import { useUITranslation } from '@vertesia/ui/i18n';
2
2
  import { SignInEmailRow, SignInProviderButton, SignInStepHeader, SignInStepLayout } from './SignInPrimitives';
3
- import { type ProviderId, providerLabel, startSignIn } from './signInUtils';
3
+ import { type ProviderId, providerLabel, type RedirectProviderId, startSignIn } from './signInUtils';
4
4
 
5
5
  interface SignInProvidersStepProps {
6
6
  email: string;
7
7
  onBack: () => void;
8
8
  onProviderClicked: (provider: ProviderId) => void;
9
+ /** The address resolved to a password tenant on retry, so the parent shows the password step. */
10
+ onPasswordRequired: (email: string) => void;
9
11
  redirectTo?: string;
10
12
  }
11
13
 
12
- const PROVIDERS: ProviderId[] = ['google', 'github', 'microsoft'];
14
+ const PROVIDERS: RedirectProviderId[] = ['google', 'github', 'microsoft'];
13
15
 
14
16
  export default function SignInProvidersStep({
15
17
  email,
16
18
  onBack,
17
19
  onProviderClicked,
20
+ onPasswordRequired,
18
21
  redirectTo,
19
22
  }: SignInProvidersStepProps) {
20
23
  const { t } = useUITranslation();
21
24
 
22
- const pick = async (provider: ProviderId) => {
25
+ const pick = async (provider: RedirectProviderId) => {
23
26
  onProviderClicked(provider);
24
- await startSignIn(provider, email, redirectTo);
27
+ const result = await startSignIn(provider, email, redirectTo);
28
+ if (!result.ok && result.reason === 'password-required') onPasswordRequired(email);
25
29
  };
26
30
 
27
31
  return (
@@ -21,6 +21,8 @@ interface SignInReturningStepProps {
21
21
  session: LastSuccessfulLogin;
22
22
  onNotYou: () => void;
23
23
  onProviderClicked: (provider: ProviderId) => void;
24
+ /** The address signs in with a password, so the parent shows the password step instead. */
25
+ onPasswordRequired: (email: string) => void;
24
26
  redirectTo?: string;
25
27
  }
26
28
 
@@ -28,6 +30,7 @@ export default function SignInReturningStep({
28
30
  session,
29
31
  onNotYou,
30
32
  onProviderClicked,
33
+ onPasswordRequired,
31
34
  redirectTo,
32
35
  }: SignInReturningStepProps) {
33
36
  const { t } = useUITranslation();
@@ -36,14 +39,23 @@ export default function SignInReturningStep({
36
39
  const avatar = <SignInInitialsBadge initials={emailInitial(session.email)} />;
37
40
  // A stored tenantName means we resolved the user's organization.
38
41
  const hasTenant = !!session.tenantName;
42
+ const lastProvider = session.lastProvider;
39
43
  const primaryLabel =
40
- session.lastProvider === 'oidc'
41
- ? t('auth.continueWithSignIn')
42
- : t('auth.continueWithProvider', { provider: providerLabel(session.lastProvider) });
44
+ lastProvider === 'password'
45
+ ? t('auth.password.continue')
46
+ : lastProvider === 'oidc'
47
+ ? t('auth.continueWithSignIn')
48
+ : t('auth.continueWithProvider', { provider: providerLabel(lastProvider) });
43
49
 
44
- const continueWith = async (provider: ProviderId) => {
45
- onProviderClicked(provider);
46
- await startSignIn(provider, session.email, redirectTo);
50
+ const continueWithLastProvider = async () => {
51
+ if (lastProvider === 'password') {
52
+ onPasswordRequired(session.email);
53
+ return;
54
+ }
55
+ onProviderClicked(lastProvider);
56
+ // The stored provider can be stale: the address may now resolve to a password tenant.
57
+ const result = await startSignIn(lastProvider, session.email, redirectTo);
58
+ if (!result.ok && result.reason === 'password-required') onPasswordRequired(session.email);
47
59
  };
48
60
 
49
61
  return (
@@ -76,9 +88,9 @@ export default function SignInReturningStep({
76
88
 
77
89
  <div className="flex flex-col gap-2">
78
90
  <SignInProviderButton
79
- provider={session.lastProvider}
91
+ provider={lastProvider}
80
92
  label={primaryLabel}
81
- onClick={() => continueWith(session.lastProvider)}
93
+ onClick={continueWithLastProvider}
82
94
  variant="filled"
83
95
  />
84
96
  <SignInStepButton variant="ghost" onClick={onNotYou}>
@@ -8,13 +8,15 @@ import {
8
8
  SignInStepLayout,
9
9
  } from './SignInPrimitives';
10
10
  import SignInWithProviderButton from './SignInWithProviderButton';
11
- import { type ProviderId, providerLabel } from './signInUtils';
11
+ import { providerLabel, type RedirectProviderId } from './signInUtils';
12
12
 
13
13
  interface SignInTenantStepProps {
14
14
  email: string;
15
15
  tenant: TenantInfo;
16
16
  onBack: () => void;
17
17
  onProviderClicked: () => void;
18
+ /** The tenant re-resolved as a password tenant, so the parent shows the password step. */
19
+ onPasswordRequired: (email: string) => void;
18
20
  redirectTo?: string;
19
21
  }
20
22
 
@@ -32,12 +34,13 @@ export default function SignInTenantStep({
32
34
  tenant,
33
35
  onBack,
34
36
  onProviderClicked,
37
+ onPasswordRequired,
35
38
  redirectTo,
36
39
  }: SignInTenantStepProps) {
37
40
  const { t } = useUITranslation();
38
41
  const tenantName = tenant.label || tenant.name || t('auth.blocked.tenantFallback');
39
42
  // Brands keep their identity; anything else routes through OIDC.
40
- const provider: ProviderId =
43
+ const provider: RedirectProviderId =
41
44
  tenant.provider === 'google' || tenant.provider === 'github' || tenant.provider === 'microsoft'
42
45
  ? tenant.provider
43
46
  : 'oidc';
@@ -68,6 +71,7 @@ export default function SignInTenantStep({
68
71
  redirectTo={redirectTo}
69
72
  variant="filled"
70
73
  onClick={onProviderClicked}
74
+ onPasswordRequired={() => onPasswordRequired(email)}
71
75
  />
72
76
  <SignInStepButton variant="ghost" onClick={onBack}>
73
77
  {t('auth.tenant.notPartOf', { name: tenantName })}
@@ -1,15 +1,17 @@
1
1
  import { useUITranslation } from '@vertesia/ui/i18n';
2
2
  import { SignInProviderButton } from './SignInPrimitives';
3
- import { type ProviderId, providerLabel, startSignIn, startSignInWithoutTenant } from './signInUtils';
3
+ import { providerLabel, type RedirectProviderId, startSignIn, startSignInWithoutTenant } from './signInUtils';
4
4
 
5
5
  interface SignInWithProviderButtonProps {
6
- provider: ProviderId;
6
+ provider: RedirectProviderId;
7
7
  /** Email for the tenant-aware flow. Omit for a no-tenant sign-in (e.g. SignInModal). */
8
8
  email?: string;
9
9
  redirectTo?: string;
10
10
  variant?: 'outline' | 'filled';
11
11
  /** Fired on click, before the redirect. */
12
12
  onClick?: () => void;
13
+ /** Fired instead of a redirect when the email resolves to a password tenant. */
14
+ onPasswordRequired?: () => void;
13
15
  }
14
16
 
15
17
  // "Continue with <provider>" button that owns its sign-in redirect.
@@ -19,6 +21,7 @@ export default function SignInWithProviderButton({
19
21
  redirectTo,
20
22
  variant = 'outline',
21
23
  onClick,
24
+ onPasswordRequired,
22
25
  }: SignInWithProviderButtonProps) {
23
26
  const { t } = useUITranslation();
24
27
  const label =
@@ -26,15 +29,16 @@ export default function SignInWithProviderButton({
26
29
  ? t('auth.continueWithSignIn')
27
30
  : t('auth.continueWithProvider', { provider: providerLabel(provider) });
28
31
 
29
- const signIn = () => {
32
+ const signIn = async () => {
30
33
  onClick?.();
31
- if (email) {
32
- void startSignIn(provider, email, redirectTo);
33
- } else {
34
+ if (!email) {
34
35
  // OIDC needs a resolved tenant; the no-email path is only hit by no-tenant buttons.
35
36
  startSignInWithoutTenant(provider, redirectTo);
37
+ return;
36
38
  }
39
+ const result = await startSignIn(provider, email, redirectTo);
40
+ if (!result.ok && result.reason === 'password-required') onPasswordRequired?.();
37
41
  };
38
42
 
39
- return <SignInProviderButton provider={provider} label={label} onClick={signIn} variant={variant} />;
43
+ return <SignInProviderButton provider={provider} label={label} onClick={() => void signIn()} variant={variant} />;
40
44
  }
@@ -15,6 +15,7 @@ import {
15
15
  import { useCallback, useEffect, useState } from 'react';
16
16
  import SignInAuthPending from './SignInAuthPending';
17
17
  import SignInEmailStep, { type TenantInfo } from './SignInEmailStep';
18
+ import SignInPasswordStep from './SignInPasswordStep';
18
19
  import SignInProvidersStep from './SignInProvidersStep';
19
20
  import SignInRecoveryStep, { type SignInRecoveryKind } from './SignInRecoveryStep';
20
21
  import SignInRestrictedEnvStep from './SignInRestrictedEnvStep';
@@ -79,6 +80,7 @@ type Mode =
79
80
  | 'email'
80
81
  | 'providers'
81
82
  | 'tenant'
83
+ | 'password'
82
84
  | 'blocked'
83
85
  | 'returning'
84
86
  | 'pending'
@@ -185,7 +187,13 @@ function SigninScreenImpl({
185
187
  const onProceedFromEmail = useCallback((e: string, t: TenantInfo | undefined) => {
186
188
  setEmail(e);
187
189
  setTenant(t);
188
- setMode(t ? 'tenant' : 'providers');
190
+ // A password tenant has no identity provider to pick; ask for its password directly.
191
+ setMode(t?.provider === 'password' ? 'password' : t ? 'tenant' : 'providers');
192
+ }, []);
193
+
194
+ const onPasswordRequired = useCallback((e: string) => {
195
+ setEmail(e);
196
+ setMode('password');
189
197
  }, []);
190
198
 
191
199
  const onBack = useCallback(() => {
@@ -336,6 +344,14 @@ function SigninScreenImpl({
336
344
  );
337
345
  } else if (mode === 'signup' && !localStorage.getItem('tenantName')) {
338
346
  content = <SignupForm onSignup={onSignup} goBack={startOver} />;
347
+ } else if (mode === 'password' && email) {
348
+ content = (
349
+ <SignInPasswordStep
350
+ email={email}
351
+ tenantName={tenant?.label || tenant?.name || storedSession?.tenantName || undefined}
352
+ onBack={onBack}
353
+ />
354
+ );
339
355
  } else if (mode === 'tenant' && tenant) {
340
356
  content = (
341
357
  <SignInTenantStep
@@ -343,13 +359,26 @@ function SigninScreenImpl({
343
359
  tenant={tenant}
344
360
  onBack={onBack}
345
361
  onProviderClicked={() => onProviderClicked((tenant.provider ?? 'oidc') as ProviderId)}
362
+ onPasswordRequired={onPasswordRequired}
346
363
  />
347
364
  );
348
365
  } else if (mode === 'providers') {
349
- content = <SignInProvidersStep email={email} onBack={onBack} onProviderClicked={onProviderClicked} />;
366
+ content = (
367
+ <SignInProvidersStep
368
+ email={email}
369
+ onBack={onBack}
370
+ onProviderClicked={onProviderClicked}
371
+ onPasswordRequired={onPasswordRequired}
372
+ />
373
+ );
350
374
  } else if (mode === 'returning' && storedSession) {
351
375
  content = (
352
- <SignInReturningStep session={storedSession} onNotYou={onNotYou} onProviderClicked={onProviderClicked} />
376
+ <SignInReturningStep
377
+ session={storedSession}
378
+ onNotYou={onNotYou}
379
+ onProviderClicked={onProviderClicked}
380
+ onPasswordRequired={onPasswordRequired}
381
+ />
353
382
  );
354
383
  } else {
355
384
  content = <SignInEmailStep initialEmail={email} onProceed={onProceedFromEmail} />;
@@ -0,0 +1,133 @@
1
+ import { FirebaseError } from 'firebase/app';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ const mocks = vi.hoisted(() => ({
5
+ auth: { tenantId: null as string | null },
6
+ setFirebaseTenant: vi.fn(),
7
+ signInWithEmailAndPassword: vi.fn(),
8
+ signInWithRedirect: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('@vertesia/ui/session', () => ({
12
+ getFirebaseAuth: () => mocks.auth,
13
+ setFirebaseTenant: mocks.setFirebaseTenant,
14
+ }));
15
+
16
+ vi.mock('firebase/auth', async (importOriginal) => ({
17
+ ...(await importOriginal<typeof import('firebase/auth')>()),
18
+ signInWithEmailAndPassword: mocks.signInWithEmailAndPassword,
19
+ signInWithRedirect: mocks.signInWithRedirect,
20
+ }));
21
+
22
+ const { passwordFailureReason, readPendingSignin, signInWithPassword, startSignIn } = await import('./signInUtils');
23
+
24
+ const PASSWORD_TENANT = {
25
+ label: 'Vertesia Reviewers',
26
+ name: 'reviewers',
27
+ firebaseTenantId: 'reviewers-abc12',
28
+ provider: 'password',
29
+ domain: ['reviewers.vertesia.io'],
30
+ };
31
+ const OIDC_TENANT = { ...PASSWORD_TENANT, label: 'Acme', name: 'acme', provider: 'oidc', domain: ['acme.com'] };
32
+ const EMAIL = 'claude@reviewers.vertesia.io';
33
+
34
+ describe('passwordFailureReason', () => {
35
+ // One answer for every credential failure, so the form cannot be used to probe which
36
+ // addresses exist or are disabled.
37
+ it.each([
38
+ 'auth/invalid-credential',
39
+ 'auth/wrong-password',
40
+ 'auth/user-not-found',
41
+ 'auth/user-disabled',
42
+ 'auth/invalid-email',
43
+ ])('folds %s into invalid-credentials', (code) => {
44
+ expect(passwordFailureReason(new FirebaseError(code, code))).toBe('invalid-credentials');
45
+ });
46
+
47
+ it('reports throttling separately', () => {
48
+ expect(passwordFailureReason(new FirebaseError('auth/too-many-requests', 'throttled'))).toBe(
49
+ 'too-many-attempts',
50
+ );
51
+ });
52
+
53
+ it('treats anything else as a generic failure', () => {
54
+ expect(passwordFailureReason(new FirebaseError('auth/network-request-failed', 'offline'))).toBe('failed');
55
+ expect(passwordFailureReason(new Error('boom'))).toBe('failed');
56
+ expect(passwordFailureReason(undefined)).toBe('failed');
57
+ });
58
+ });
59
+
60
+ describe('signInWithPassword', () => {
61
+ beforeEach(() => {
62
+ mocks.setFirebaseTenant.mockReset();
63
+ mocks.signInWithEmailAndPassword.mockReset();
64
+ mocks.signInWithRedirect.mockReset();
65
+ });
66
+
67
+ afterEach(() => {
68
+ sessionStorage.clear();
69
+ localStorage.clear();
70
+ });
71
+
72
+ it('signs a password-tenant user in place and records the pending sign-in', async () => {
73
+ mocks.setFirebaseTenant.mockResolvedValue(PASSWORD_TENANT);
74
+ mocks.signInWithEmailAndPassword.mockResolvedValue({});
75
+
76
+ await expect(signInWithPassword(EMAIL, 'secret')).resolves.toEqual({ ok: true });
77
+
78
+ expect(mocks.setFirebaseTenant).toHaveBeenCalledWith(EMAIL);
79
+ expect(mocks.signInWithEmailAndPassword).toHaveBeenCalledWith(mocks.auth, EMAIL, 'secret');
80
+ expect(readPendingSignin()).toEqual({
81
+ email: EMAIL,
82
+ provider: 'password',
83
+ tenantName: 'Vertesia Reviewers',
84
+ });
85
+ expect(mocks.signInWithRedirect).not.toHaveBeenCalled();
86
+ });
87
+
88
+ // The password form is reachable only through tenant resolution, but the helper re-checks: an
89
+ // address that resolves to a federated tenant must never be offered a password.
90
+ it('refuses an address whose tenant is not a password tenant', async () => {
91
+ mocks.setFirebaseTenant.mockResolvedValue(OIDC_TENANT);
92
+
93
+ await expect(signInWithPassword('someone@acme.com', 'secret')).resolves.toEqual({
94
+ ok: false,
95
+ reason: 'not-password-tenant',
96
+ });
97
+ expect(mocks.signInWithEmailAndPassword).not.toHaveBeenCalled();
98
+ });
99
+
100
+ it('fails without signing in when the tenant cannot be resolved', async () => {
101
+ mocks.setFirebaseTenant.mockResolvedValue(undefined);
102
+
103
+ await expect(signInWithPassword(EMAIL, 'secret')).resolves.toEqual({ ok: false, reason: 'failed' });
104
+ expect(mocks.signInWithEmailAndPassword).not.toHaveBeenCalled();
105
+ });
106
+
107
+ it('clears the pending sign-in when Firebase rejects the credential', async () => {
108
+ mocks.setFirebaseTenant.mockResolvedValue(PASSWORD_TENANT);
109
+ mocks.signInWithEmailAndPassword.mockRejectedValue(new FirebaseError('auth/invalid-credential', 'bad'));
110
+
111
+ await expect(signInWithPassword(EMAIL, 'wrong')).resolves.toEqual({
112
+ ok: false,
113
+ reason: 'invalid-credentials',
114
+ });
115
+ expect(readPendingSignin()).toBeNull();
116
+ });
117
+ });
118
+
119
+ describe('startSignIn', () => {
120
+ beforeEach(() => {
121
+ mocks.setFirebaseTenant.mockReset();
122
+ mocks.signInWithRedirect.mockReset();
123
+ });
124
+
125
+ // A returning session's stored provider can go stale; a password tenant has nowhere to redirect.
126
+ it('does not redirect when the address resolves to a password tenant', async () => {
127
+ mocks.setFirebaseTenant.mockResolvedValue(PASSWORD_TENANT);
128
+
129
+ await expect(startSignIn('google', EMAIL)).resolves.toEqual({ ok: false, reason: 'password-required' });
130
+ expect(mocks.signInWithRedirect).not.toHaveBeenCalled();
131
+ expect(readPendingSignin()).toBeNull();
132
+ });
133
+ });