@tinyrack/issuary-server 0.22.0 → 0.24.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 (333) hide show
  1. package/dist/entities/oauth-client.entity.d.ts +0 -18
  2. package/dist/entities/oauth-client.entity.d.ts.map +1 -1
  3. package/dist/entities/oauth-code.entity.d.ts +0 -18
  4. package/dist/entities/oauth-code.entity.d.ts.map +1 -1
  5. package/dist/entities/oauth-device-code.entity.d.ts +0 -18
  6. package/dist/entities/oauth-device-code.entity.d.ts.map +1 -1
  7. package/dist/entities/revoked-token.entity.d.ts +0 -18
  8. package/dist/entities/revoked-token.entity.d.ts.map +1 -1
  9. package/dist/entities/terms-content.entity.d.ts +0 -9
  10. package/dist/entities/terms-content.entity.d.ts.map +1 -1
  11. package/dist/entities/terms.entity.d.ts +0 -9
  12. package/dist/entities/terms.entity.d.ts.map +1 -1
  13. package/dist/entities/user-consent.entity.d.ts +0 -18
  14. package/dist/entities/user-consent.entity.d.ts.map +1 -1
  15. package/dist/entities/user-terms-consent.entity.d.ts +0 -9
  16. package/dist/entities/user-terms-consent.entity.d.ts.map +1 -1
  17. package/dist/entities/user-totp-recovery-code.entity.d.ts +0 -9
  18. package/dist/entities/user-totp-recovery-code.entity.d.ts.map +1 -1
  19. package/dist/entities/user-totp.entity.d.ts +0 -9
  20. package/dist/entities/user-totp.entity.d.ts.map +1 -1
  21. package/dist/entities/user.entity.d.ts +0 -9
  22. package/dist/entities/user.entity.d.ts.map +1 -1
  23. package/dist/entities/user.entity.js +0 -4
  24. package/dist/entities/user.entity.js.map +1 -1
  25. package/dist/entrypoints/app.d.ts +0 -59
  26. package/dist/entrypoints/app.d.ts.map +1 -1
  27. package/dist/entrypoints/database/postgres/compiled-functions.d.ts +20 -23
  28. package/dist/entrypoints/database/postgres/compiled-functions.d.ts.map +1 -1
  29. package/dist/entrypoints/database/postgres/compiled-functions.js +104 -134
  30. package/dist/entrypoints/database/postgres/compiled-functions.js.map +1 -1
  31. package/dist/entrypoints/database/sqlite/compiled-functions.d.ts +20 -23
  32. package/dist/entrypoints/database/sqlite/compiled-functions.d.ts.map +1 -1
  33. package/dist/entrypoints/database/sqlite/compiled-functions.js +104 -134
  34. package/dist/entrypoints/database/sqlite/compiled-functions.js.map +1 -1
  35. package/dist/lib/config/branding.d.ts +0 -4
  36. package/dist/lib/config/branding.d.ts.map +1 -1
  37. package/dist/lib/config/branding.js +0 -14
  38. package/dist/lib/config/branding.js.map +1 -1
  39. package/dist/lib/config/resolved.d.ts +0 -3
  40. package/dist/lib/config/resolved.d.ts.map +1 -1
  41. package/dist/lib/config/security.d.ts +0 -2
  42. package/dist/lib/config/security.d.ts.map +1 -1
  43. package/dist/lib/config/security.js +0 -5
  44. package/dist/lib/config/security.js.map +1 -1
  45. package/dist/middleware/auth.d.ts +0 -8
  46. package/dist/middleware/auth.d.ts.map +1 -1
  47. package/dist/middleware/auth.js +0 -64
  48. package/dist/middleware/auth.js.map +1 -1
  49. package/dist/middleware/session.d.ts +0 -9
  50. package/dist/middleware/session.d.ts.map +1 -1
  51. package/dist/middleware/session.js +0 -18
  52. package/dist/middleware/session.js.map +1 -1
  53. package/dist/migrations/postgres/Migration20260825140000_drop_password_reset_required.d.ts +6 -0
  54. package/dist/migrations/postgres/Migration20260825140000_drop_password_reset_required.d.ts.map +1 -0
  55. package/dist/migrations/postgres/Migration20260825140000_drop_password_reset_required.js +19 -0
  56. package/dist/migrations/postgres/Migration20260825140000_drop_password_reset_required.js.map +1 -0
  57. package/dist/migrations/postgres/index.d.ts.map +1 -1
  58. package/dist/migrations/postgres/index.js +2 -0
  59. package/dist/migrations/postgres/index.js.map +1 -1
  60. package/dist/migrations/sqlite/Migration20260825140000_drop_password_reset_required.d.ts +6 -0
  61. package/dist/migrations/sqlite/Migration20260825140000_drop_password_reset_required.d.ts.map +1 -0
  62. package/dist/migrations/sqlite/Migration20260825140000_drop_password_reset_required.js +19 -0
  63. package/dist/migrations/sqlite/Migration20260825140000_drop_password_reset_required.js.map +1 -0
  64. package/dist/migrations/sqlite/index.d.ts.map +1 -1
  65. package/dist/migrations/sqlite/index.js +2 -0
  66. package/dist/migrations/sqlite/index.js.map +1 -1
  67. package/dist/repositories/oauth-code.repository.d.ts +2 -2
  68. package/dist/repositories/oauth-code.repository.d.ts.map +1 -1
  69. package/dist/repositories/oauth-code.repository.js +3 -7
  70. package/dist/repositories/oauth-code.repository.js.map +1 -1
  71. package/dist/repositories/oauth-device-code.repository.d.ts +4 -4
  72. package/dist/repositories/oauth-device-code.repository.d.ts.map +1 -1
  73. package/dist/repositories/oauth-device-code.repository.js +6 -18
  74. package/dist/repositories/oauth-device-code.repository.js.map +1 -1
  75. package/dist/repositories/user-totp-recovery-code.repository.d.ts +1 -7
  76. package/dist/repositories/user-totp-recovery-code.repository.d.ts.map +1 -1
  77. package/dist/repositories/user-totp-recovery-code.repository.js +1 -2
  78. package/dist/repositories/user-totp-recovery-code.repository.js.map +1 -1
  79. package/dist/repositories/user-totp.repository.d.ts +0 -9
  80. package/dist/repositories/user-totp.repository.d.ts.map +1 -1
  81. package/dist/routes/api/auth/index.d.ts +0 -52
  82. package/dist/routes/api/auth/index.d.ts.map +1 -1
  83. package/dist/routes/api/auth/index.js +0 -2
  84. package/dist/routes/api/auth/index.js.map +1 -1
  85. package/dist/routes/api/auth/passkey/verify/post.d.ts +0 -37
  86. package/dist/routes/api/auth/passkey/verify/post.d.ts.map +1 -1
  87. package/dist/routes/api/auth/passkey/verify/post.js +2 -8
  88. package/dist/routes/api/auth/passkey/verify/post.js.map +1 -1
  89. package/dist/routes/api/config/get.d.ts +0 -4
  90. package/dist/routes/api/config/get.d.ts.map +1 -1
  91. package/dist/routes/api/config/get.js +0 -2
  92. package/dist/routes/api/config/get.js.map +1 -1
  93. package/dist/routes/api/config/index.d.ts +0 -4
  94. package/dist/routes/api/config/index.d.ts.map +1 -1
  95. package/dist/routes/api/index.d.ts +0 -56
  96. package/dist/routes/api/index.d.ts.map +1 -1
  97. package/dist/routes/index.d.ts +0 -56
  98. package/dist/routes/index.d.ts.map +1 -1
  99. package/dist/routes/oauth/device/get-post.js +5 -5
  100. package/dist/routes/oauth/device/get-post.js.map +1 -1
  101. package/dist/schemas/error.d.ts +0 -25
  102. package/dist/schemas/error.d.ts.map +1 -1
  103. package/dist/schemas/error.js +0 -1
  104. package/dist/schemas/error.js.map +1 -1
  105. package/dist/schemas/response.d.ts +0 -24
  106. package/dist/schemas/response.d.ts.map +1 -1
  107. package/dist/schemas/response.js +0 -13
  108. package/dist/schemas/response.js.map +1 -1
  109. package/dist/seeders/config.seeder.js +0 -1
  110. package/dist/seeders/config.seeder.js.map +1 -1
  111. package/dist/services/container.d.ts +0 -3
  112. package/dist/services/container.d.ts.map +1 -1
  113. package/dist/services/container.js +0 -15
  114. package/dist/services/container.js.map +1 -1
  115. package/dist/services/oauth-client.service.d.ts +0 -1
  116. package/dist/services/oauth-client.service.d.ts.map +1 -1
  117. package/dist/services/oauth-client.service.js +2 -17
  118. package/dist/services/oauth-client.service.js.map +1 -1
  119. package/dist/services/oauth-connect.service.d.ts.map +1 -1
  120. package/dist/services/oauth-connect.service.js +0 -4
  121. package/dist/services/oauth-connect.service.js.map +1 -1
  122. package/dist/services/oauth-token.service.d.ts.map +1 -1
  123. package/dist/services/oauth-token.service.js +4 -4
  124. package/dist/services/oauth-token.service.js.map +1 -1
  125. package/dist/services/password-auth.service.d.ts.map +1 -1
  126. package/dist/services/password-auth.service.js +3 -12
  127. package/dist/services/password-auth.service.js.map +1 -1
  128. package/dist/services/security.service.d.ts +0 -8
  129. package/dist/services/security.service.d.ts.map +1 -1
  130. package/dist/services/security.service.js +21 -82
  131. package/dist/services/security.service.js.map +1 -1
  132. package/dist/services/totp.service.js +2 -2
  133. package/dist/services/totp.service.js.map +1 -1
  134. package/package.json +2 -1
  135. package/public/assets/2fa-6btYq1AL.js +2 -0
  136. package/public/assets/{2fa-C37MUkye.js.map → 2fa-6btYq1AL.js.map} +1 -1
  137. package/public/assets/2fa-D3ieXkuh.js +2 -0
  138. package/public/assets/{2fa-kiqzGSz2.js.map → 2fa-D3ieXkuh.js.map} +1 -1
  139. package/public/assets/2fa-Dxh4Kxy-.js +2 -0
  140. package/public/assets/{2fa-BgveHSB_.js.map → 2fa-Dxh4Kxy-.js.map} +1 -1
  141. package/public/assets/2fa-UcpXAI9J.js +2 -0
  142. package/public/assets/{2fa-UL_cNRnQ.js.map → 2fa-UcpXAI9J.js.map} +1 -1
  143. package/public/assets/{DialogHandle-Zvz8SgS0.js → DialogHandle-tiMUnjZQ.js} +2 -2
  144. package/public/assets/{DialogHandle-Zvz8SgS0.js.map → DialogHandle-tiMUnjZQ.js.map} +1 -1
  145. package/public/assets/admin-B8RQbf4_.js +2 -0
  146. package/public/assets/admin-B8RQbf4_.js.map +1 -0
  147. package/public/assets/admin-users-Dz1QNdy3.js +2 -0
  148. package/public/assets/{admin-users-CwTJdiih.js.map → admin-users-Dz1QNdy3.js.map} +1 -1
  149. package/public/assets/alert-leqyvHOp.js.map +1 -1
  150. package/public/assets/{auth-choice-row-BlSsUI7d.js → auth-choice-row-DWzfcZQY.js} +2 -2
  151. package/public/assets/{auth-choice-row-BlSsUI7d.js.map → auth-choice-row-DWzfcZQY.js.map} +1 -1
  152. package/public/assets/{auth-field-HwxENJnB.js → auth-field-DsLGh1U-.js} +2 -2
  153. package/public/assets/{auth-field-HwxENJnB.js.map → auth-field-DsLGh1U-.js.map} +1 -1
  154. package/public/assets/auth-layout-DGTylGtX.js +2 -0
  155. package/public/assets/auth-layout-DGTylGtX.js.map +1 -0
  156. package/public/assets/{auth-page-header-CIs08f5Y.js → auth-page-header-COvqWINf.js} +2 -2
  157. package/public/assets/auth-page-header-COvqWINf.js.map +1 -0
  158. package/public/assets/authorization-context-banner-DVx1TsPY.js +2 -0
  159. package/public/assets/authorization-context-banner-DVx1TsPY.js.map +1 -0
  160. package/public/assets/{avatar-CttWiIv3.js → avatar-BCLugfaZ.js} +2 -2
  161. package/public/assets/{avatar-CttWiIv3.js.map → avatar-BCLugfaZ.js.map} +1 -1
  162. package/public/assets/badge-CsOfUji0.js.map +1 -1
  163. package/public/assets/{checkbox-CJrRV8S1.js → checkbox-CFVDS3zJ.js} +2 -2
  164. package/public/assets/{checkbox-CJrRV8S1.js.map → checkbox-CFVDS3zJ.js.map} +1 -1
  165. package/public/assets/config-DTQCUlfZ.js +2 -0
  166. package/public/assets/config-DTQCUlfZ.js.map +1 -0
  167. package/public/assets/{consent-BIhxtXHx.js → consent-CKOohdYI.js} +2 -2
  168. package/public/assets/{consent-BIhxtXHx.js.map → consent-CKOohdYI.js.map} +1 -1
  169. package/public/assets/{consent-DRmENn26.js → consent-ChiWc7Ql.js} +2 -2
  170. package/public/assets/{consent-DRmENn26.js.map → consent-ChiWc7Ql.js.map} +1 -1
  171. package/public/assets/createLucideIcon-BPpAIXR_.js.map +1 -1
  172. package/public/assets/{email-CVRDeJ78.js → email-Crjg1dcr.js} +2 -2
  173. package/public/assets/{email-CVRDeJ78.js.map → email-Crjg1dcr.js.map} +1 -1
  174. package/public/assets/email-UcpXAI9J.js +2 -0
  175. package/public/assets/{email-UL_cNRnQ.js.map → email-UcpXAI9J.js.map} +1 -1
  176. package/public/assets/error-VfMv226c.js +2 -0
  177. package/public/assets/{error-2VPY5w8V.js.map → error-VfMv226c.js.map} +1 -1
  178. package/public/assets/{field-DDt6F9TZ.js → field-89zKYDVL.js} +2 -2
  179. package/public/assets/{field-DDt6F9TZ.js.map → field-89zKYDVL.js.map} +1 -1
  180. package/public/assets/forgot-Dr88fVMq.js +2 -0
  181. package/public/assets/{forgot-DzDcduho.js.map → forgot-Dr88fVMq.js.map} +1 -1
  182. package/public/assets/forgot-UcpXAI9J.js +2 -0
  183. package/public/assets/{forgot-UL_cNRnQ.js.map → forgot-UcpXAI9J.js.map} +1 -1
  184. package/public/assets/{index-DZyHRhbQ.css → index-CDD8owGK.css} +1 -1
  185. package/public/assets/index-CZ0rWM7l.js +11 -0
  186. package/public/assets/index-CZ0rWM7l.js.map +1 -0
  187. package/public/assets/{initial-avatar-CZzHAPm3.js → initial-avatar-ocwNWL38.js} +2 -2
  188. package/public/assets/{initial-avatar-CZzHAPm3.js.map → initial-avatar-ocwNWL38.js.map} +1 -1
  189. package/public/assets/{input-CNRpSR08.js → input-GPigtjgi.js} +2 -2
  190. package/public/assets/{input-CNRpSR08.js.map → input-GPigtjgi.js.map} +1 -1
  191. package/public/assets/{labeled-separator-fnKySKv2.js → labeled-separator-CV7nb0XW.js} +2 -2
  192. package/public/assets/{labeled-separator-fnKySKv2.js.map → labeled-separator-CV7nb0XW.js.map} +1 -1
  193. package/public/assets/link-C-WdTj3F.js.map +1 -1
  194. package/public/assets/login-CQeTLmIu.js +2 -0
  195. package/public/assets/login-CQeTLmIu.js.map +1 -0
  196. package/public/assets/login-UcpXAI9J.js +2 -0
  197. package/public/assets/login-UcpXAI9J.js.map +1 -0
  198. package/public/assets/{logout-CHdmF_16.js → logout-B5T52P8u.js} +2 -2
  199. package/public/assets/{logout-CHdmF_16.js.map → logout-B5T52P8u.js.map} +1 -1
  200. package/public/assets/{modal-sCM_zT82.js → modal-BV3gpI-p.js} +2 -2
  201. package/public/assets/{modal-sCM_zT82.js.map → modal-BV3gpI-p.js.map} +1 -1
  202. package/public/assets/{mutationOptions-CrswitPs.js → mutationOptions-DyYFUbyn.js} +2 -2
  203. package/public/assets/{mutationOptions-CrswitPs.js.map → mutationOptions-DyYFUbyn.js.map} +1 -1
  204. package/public/assets/passkey-Bf7n49Z7.js +2 -0
  205. package/public/assets/{passkey-CEx1VZ5R.js.map → passkey-Bf7n49Z7.js.map} +1 -1
  206. package/public/assets/passkey-CT3G7pCL.js +2 -0
  207. package/public/assets/{passkey-CU8x9J2w.js.map → passkey-CT3G7pCL.js.map} +1 -1
  208. package/public/assets/passkey-J1qnYp9A.js +2 -0
  209. package/public/assets/{passkey-B2Blw47e.js.map → passkey-J1qnYp9A.js.map} +1 -1
  210. package/public/assets/passkey-UcpXAI9J.js +2 -0
  211. package/public/assets/{passkey-UL_cNRnQ.js.map → passkey-UcpXAI9J.js.map} +1 -1
  212. package/public/assets/password-BKI5GTRs.js +2 -0
  213. package/public/assets/password-BKI5GTRs.js.map +1 -0
  214. package/public/assets/password-UcpXAI9J.js +2 -0
  215. package/public/assets/password-UcpXAI9J.js.map +1 -0
  216. package/public/assets/password-reset-sU5KkBKi.js +2 -0
  217. package/public/assets/{password-reset-CIacHnvA.js.map → password-reset-sU5KkBKi.js.map} +1 -1
  218. package/public/assets/{password-strength-C0Zkrrct.js → password-strength-DTWF07Px.js} +2 -2
  219. package/public/assets/{password-strength-C0Zkrrct.js.map → password-strength-DTWF07Px.js.map} +1 -1
  220. package/public/assets/{pin-input-BlD4tPZJ.js → pin-input-BGOLfkwL.js} +2 -2
  221. package/public/assets/{pin-input-BlD4tPZJ.js.map → pin-input-BGOLfkwL.js.map} +1 -1
  222. package/public/assets/{profile-D43Yq-6S.js → profile-DSTMtoUf.js} +2 -2
  223. package/public/assets/{profile-D43Yq-6S.js.map → profile-DSTMtoUf.js.map} +1 -1
  224. package/public/assets/profile-RpDmbWNA.js +2 -0
  225. package/public/assets/profile-RpDmbWNA.js.map +1 -0
  226. package/public/assets/provider-logos-u1YURWQr.js.map +1 -1
  227. package/public/assets/recovery-DbgpqG6f.js +2 -0
  228. package/public/assets/{recovery-CLExnfYX.js.map → recovery-DbgpqG6f.js.map} +1 -1
  229. package/public/assets/register-B2Lixapy.js +2 -0
  230. package/public/assets/{register-CZUyWXRO.js.map → register-B2Lixapy.js.map} +1 -1
  231. package/public/assets/register-UcpXAI9J.js +2 -0
  232. package/public/assets/{register-UL_cNRnQ.js.map → register-UcpXAI9J.js.map} +1 -1
  233. package/public/assets/reset-C-Qjl2Qy.js +2 -0
  234. package/public/assets/reset-C-Qjl2Qy.js.map +1 -0
  235. package/public/assets/reset-UcpXAI9J.js +2 -0
  236. package/public/assets/{reset-UL_cNRnQ.js.map → reset-UcpXAI9J.js.map} +1 -1
  237. package/public/assets/{select-CWdSzsVo.js → select-BKx4bF4A.js} +2 -2
  238. package/public/assets/{select-CWdSzsVo.js.map → select-BKx4bF4A.js.map} +1 -1
  239. package/public/assets/select-UcpXAI9J.js +2 -0
  240. package/public/assets/{select-UL_cNRnQ.js.map → select-UcpXAI9J.js.map} +1 -1
  241. package/public/assets/{separator-BWGym8uM.js → separator-B0Bvh1he.js} +2 -2
  242. package/public/assets/{separator-BWGym8uM.js.map → separator-B0Bvh1he.js.map} +1 -1
  243. package/public/assets/shell-header-bar-CgyNXbPS.js +2 -0
  244. package/public/assets/shell-header-bar-CgyNXbPS.js.map +1 -0
  245. package/public/assets/{terms-D43Yq-6S.js → terms-DSTMtoUf.js} +2 -2
  246. package/public/assets/{terms-D43Yq-6S.js.map → terms-DSTMtoUf.js.map} +1 -1
  247. package/public/assets/{terms-COVFmzyl.js → terms-Vgpg5IZK.js} +2 -2
  248. package/public/assets/{terms-COVFmzyl.js.map → terms-Vgpg5IZK.js.map} +1 -1
  249. package/public/assets/{terms-checkbox-list-Cw0zB-lO.js → terms-checkbox-list-DqbXU4qw.js} +2 -2
  250. package/public/assets/{terms-checkbox-list-Cw0zB-lO.js.map → terms-checkbox-list-DqbXU4qw.js.map} +1 -1
  251. package/public/assets/{totp-BfCm6IWl.js → totp-BNZ904Zm.js} +2 -2
  252. package/public/assets/{totp-BfCm6IWl.js.map → totp-BNZ904Zm.js.map} +1 -1
  253. package/public/assets/totp-Cjns-PoB.js +2 -0
  254. package/public/assets/{totp-BFJEXxk0.js.map → totp-Cjns-PoB.js.map} +1 -1
  255. package/public/assets/totp-g0-s50HZ.js +2 -0
  256. package/public/assets/{totp-MINhsVxT.js.map → totp-g0-s50HZ.js.map} +1 -1
  257. package/public/assets/use-totp-setup-CtTaklH-.js +3 -0
  258. package/public/assets/{use-totp-setup-C7Zk_2IY.js.map → use-totp-setup-CtTaklH-.js.map} +1 -1
  259. package/public/assets/{useAriaLabelledBy-CiOCg6X5.js → useAriaLabelledBy-7uIPXK-0.js} +2 -2
  260. package/public/assets/{useAriaLabelledBy-CiOCg6X5.js.map → useAriaLabelledBy-7uIPXK-0.js.map} +1 -1
  261. package/public/assets/{useMutation-ZLFeJg75.js → useMutation-BWZ0G4Td.js} +2 -2
  262. package/public/assets/{useMutation-ZLFeJg75.js.map → useMutation-BWZ0G4Td.js.map} +1 -1
  263. package/public/assets/useQuery-pOdkMV4B.js +2 -0
  264. package/public/assets/{useQuery-BvMz34BW.js.map → useQuery-pOdkMV4B.js.map} +1 -1
  265. package/public/assets/users-B1hvVo3B.js +2 -0
  266. package/public/assets/users-B1hvVo3B.js.map +1 -0
  267. package/public/index.html +4 -5
  268. package/dist/routes/api/auth/password/reset-required/post.d.ts +0 -18
  269. package/dist/routes/api/auth/password/reset-required/post.d.ts.map +0 -1
  270. package/dist/routes/api/auth/password/reset-required/post.js +0 -39
  271. package/dist/routes/api/auth/password/reset-required/post.js.map +0 -1
  272. package/dist/services/legacy-credential-retirement.service.d.ts +0 -20
  273. package/dist/services/legacy-credential-retirement.service.d.ts.map +0 -1
  274. package/dist/services/legacy-credential-retirement.service.js +0 -90
  275. package/dist/services/legacy-credential-retirement.service.js.map +0 -1
  276. package/public/assets/2fa-BgveHSB_.js +0 -2
  277. package/public/assets/2fa-C37MUkye.js +0 -2
  278. package/public/assets/2fa-UL_cNRnQ.js +0 -2
  279. package/public/assets/2fa-kiqzGSz2.js +0 -2
  280. package/public/assets/admin-DbDuu6AI.js +0 -2
  281. package/public/assets/admin-DbDuu6AI.js.map +0 -1
  282. package/public/assets/admin-users-CwTJdiih.js +0 -2
  283. package/public/assets/auth-layout-CoeqNhTT.js +0 -2
  284. package/public/assets/auth-layout-CoeqNhTT.js.map +0 -1
  285. package/public/assets/auth-page-header-CIs08f5Y.js.map +0 -1
  286. package/public/assets/authorization-context-banner-BbtQmMYH.js +0 -2
  287. package/public/assets/authorization-context-banner-BbtQmMYH.js.map +0 -1
  288. package/public/assets/config-v3-oy_ef.js +0 -2
  289. package/public/assets/config-v3-oy_ef.js.map +0 -1
  290. package/public/assets/email-UL_cNRnQ.js +0 -2
  291. package/public/assets/error-2VPY5w8V.js +0 -2
  292. package/public/assets/forgot-DzDcduho.js +0 -2
  293. package/public/assets/forgot-UL_cNRnQ.js +0 -2
  294. package/public/assets/index-gliHKncz.js +0 -11
  295. package/public/assets/index-gliHKncz.js.map +0 -1
  296. package/public/assets/link-BN90emXr.js +0 -2
  297. package/public/assets/link-BN90emXr.js.map +0 -1
  298. package/public/assets/lock-keyhole-Cz72xkCn.js +0 -2
  299. package/public/assets/lock-keyhole-Cz72xkCn.js.map +0 -1
  300. package/public/assets/login-C6m2qSUt.js +0 -2
  301. package/public/assets/login-C6m2qSUt.js.map +0 -1
  302. package/public/assets/login-UL_cNRnQ.js +0 -2
  303. package/public/assets/login-UL_cNRnQ.js.map +0 -1
  304. package/public/assets/passkey-B2Blw47e.js +0 -2
  305. package/public/assets/passkey-CEx1VZ5R.js +0 -2
  306. package/public/assets/passkey-CU8x9J2w.js +0 -2
  307. package/public/assets/passkey-UL_cNRnQ.js +0 -2
  308. package/public/assets/password-BhU_a0cR.js +0 -2
  309. package/public/assets/password-BhU_a0cR.js.map +0 -1
  310. package/public/assets/password-UL_cNRnQ.js +0 -2
  311. package/public/assets/password-UL_cNRnQ.js.map +0 -1
  312. package/public/assets/password-reset-CIacHnvA.js +0 -2
  313. package/public/assets/profile-D1cEjfs8.js +0 -2
  314. package/public/assets/profile-D1cEjfs8.js.map +0 -1
  315. package/public/assets/recovery-CLExnfYX.js +0 -2
  316. package/public/assets/register-CZUyWXRO.js +0 -2
  317. package/public/assets/register-UL_cNRnQ.js +0 -2
  318. package/public/assets/reset-N3RQTYEB.js +0 -2
  319. package/public/assets/reset-N3RQTYEB.js.map +0 -1
  320. package/public/assets/reset-UL_cNRnQ.js +0 -2
  321. package/public/assets/reset-required-Cv5edDS6.js +0 -2
  322. package/public/assets/reset-required-Cv5edDS6.js.map +0 -1
  323. package/public/assets/reset-required-UL_cNRnQ.js +0 -2
  324. package/public/assets/reset-required-UL_cNRnQ.js.map +0 -1
  325. package/public/assets/select-UL_cNRnQ.js +0 -2
  326. package/public/assets/shell-header-bar-C9AB5lT-.js +0 -2
  327. package/public/assets/shell-header-bar-C9AB5lT-.js.map +0 -1
  328. package/public/assets/totp-BFJEXxk0.js +0 -2
  329. package/public/assets/totp-MINhsVxT.js +0 -2
  330. package/public/assets/use-totp-setup-C7Zk_2IY.js +0 -3
  331. package/public/assets/useQuery-BvMz34BW.js +0 -2
  332. package/public/assets/users-COM7u3A-.js +0 -2
  333. package/public/assets/users-COM7u3A-.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{q as e}from"./createLucideIcon-BPpAIXR_.js";import{I as t}from"./index-gliHKncz.js";var n=e();function r(e){return(0,n.jsx)(t,{...e,onUnauthorized:()=>{window.location.href=`/login`}})}export{r as errorComponent};
2
- //# sourceMappingURL=terms-D43Yq-6S.js.map
1
+ import{q as e}from"./createLucideIcon-BPpAIXR_.js";import{I as t}from"./index-CZ0rWM7l.js";var n=e();function r(e){return(0,n.jsx)(t,{...e,onUnauthorized:()=>{window.location.href=`/login`}})}export{r as errorComponent};
2
+ //# sourceMappingURL=terms-DSTMtoUf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terms-D43Yq-6S.js","names":["RouteErrorFallback","TermsError","props","ErrorComponentProps","window","location","href","errorComponent"],"sources":["../../../frontend/src/routes/terms/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n redirect,\n useRouter,\n} from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TriangleAlertIcon } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport {\n TermsCheckboxList,\n type TermsConsentsField,\n} from '#frontend/components/terms/terms-checkbox-list.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { LabeledSeparator } from '#frontend/components/ui/labeled-separator.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { OAuthSearchSchema } from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport {\n getTermsQueryOptions,\n type TermsConsentItem,\n termsConsentMutationOptions,\n} from '#frontend/queries/terms.ts';\n\nconst TermsSearchSchema = OAuthSearchSchema.extend({\n redirect: z.string().optional(),\n lang: z.string().optional(),\n mode: z.enum(['normal', 'complete_registration']).optional(),\n registration_token: z.string().optional(),\n});\n\nfunction TermsError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n window.location.href = '/login';\n }}\n />\n );\n}\n\nexport const Route = createFileRoute('/terms/')({\n component: Terms,\n errorComponent: TermsError,\n validateSearch: TermsSearchSchema,\n beforeLoad: async ({ context, search }) => {\n // When mode=complete_registration, user is completing OAuth signup.\n // The registration_token in the URL references a DB record with OAuth data.\n // Skip auth check — backend will validate the token on consent submission.\n if (search.mode === 'complete_registration') {\n return;\n }\n\n // Standard mode: requires authentication\n // Users are redirected here after OAuth login when they need to agree to terms\n if (!context.user) {\n throw redirect({\n to: '/login',\n });\n }\n },\n loaderDeps: ({ search }) => ({\n lang: search.lang,\n }),\n loader: async ({ context, deps }) => {\n const lang = deps.lang ?? context.i18n.language;\n await Promise.all([\n context.queryClient.ensureQueryData(getTermsQueryOptions(lang)),\n context.queryClient.ensureQueryData(appConfigQueryOptions),\n ]);\n },\n});\n\nfunction Terms() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n const lang = search.lang ?? i18n.language;\n const termsQuery = useSuspenseQuery(getTermsQueryOptions(lang));\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n\n // Separate explicit and implicit terms\n const explicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'explicit'),\n [termsQuery.data.terms],\n );\n\n const implicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'implicit'),\n [termsQuery.data.terms],\n );\n\n const hasExplicitTerms = explicitTerms.length > 0;\n\n // Schema only validates explicit terms (implicit are auto-agreed)\n const termsSchema = useMemo(\n () =>\n z.object({\n termsConsents: z.object(\n Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n term.required\n ? z.literal(true, {\n message: t('validation.terms.required'),\n })\n : z.boolean(),\n ]),\n ),\n ),\n }),\n [t, explicitTerms],\n );\n\n type TermsFormValues = TermsConsentsField;\n\n const {\n handleSubmit,\n control,\n setValue,\n formState: { errors },\n } = useForm<TermsFormValues>({\n defaultValues: {\n termsConsents: Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n !!(term.userConsent?.agreed && !term.userConsent.requiresUpdate),\n ]),\n ),\n },\n resolver: standardSchemaResolver(termsSchema),\n mode: 'onChange',\n });\n\n const consentMutation = useMutation({\n ...termsConsentMutationOptions,\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n await queryClient.fetchQuery(getSessionQueryOptions);\n await tick();\n\n // Redirect after successful consent\n if (search.redirect) {\n window.location.href = search.redirect;\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n });\n\n const onSubmit = (values: TermsFormValues) => {\n const explicitConsents = Object.entries(\n values.termsConsents,\n ).map<TermsConsentItem>(([termsId, agreed]) => ({\n termsId,\n agreed,\n consentType: 'explicit',\n }));\n\n const implicitConsents = implicitTerms.map<TermsConsentItem>((term) => ({\n termsId: term.id,\n agreed: true,\n consentType: 'implicit',\n }));\n\n consentMutation.mutate({\n consents: [...explicitConsents, ...implicitConsents],\n ...(search.registration_token && {\n registrationToken: search.registration_token,\n }),\n });\n };\n\n return (\n <AuthLayout width=\"wide\">\n <AuthPageHeader title={t('terms.title')} />\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {implicitNotice && hasExplicitTerms && (\n <LabeledSeparator label={t('terms.additionalOptionalConsent')} />\n )}\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n {/* Explicit terms with checkboxes */}\n {hasExplicitTerms && (\n <TermsCheckboxList\n control={control}\n disabled={consentMutation.isPending}\n errors={errors}\n setValue={setValue}\n terms={explicitTerms}\n />\n )}\n\n {/* Error message */}\n {consentMutation.isError && (\n <Alert icon={TriangleAlertIcon} type=\"error\">\n {t('terms.error.submitFailed')}\n </Alert>\n )}\n\n {/*\n Sticky, like consent: a long list of terms would otherwise push the\n only action off the bottom of the screen.\n */}\n <div className=\"sticky bottom-0 flex gap-tinyrack-sm border-tinyrack-border border-t-tinyrack-default bg-tinyrack-surface/80 py-tinyrack-md backdrop-blur-sm\">\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={consentMutation.isPending}\n loadingLabel={t('terms.submit')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('terms.submit')}\n </TRButton>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"qGA4CA,SAASC,EAAWC,EAA4B,CAC9C,OACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,GAAIA,EACJ,mBAAsB,CACpBE,OAAOC,SAASC,KAAO,QACzB,CAAE,CAAA,CAGR"}
1
+ {"version":3,"file":"terms-DSTMtoUf.js","names":["RouteErrorFallback","TermsError","props","ErrorComponentProps","window","location","href","errorComponent"],"sources":["../../../frontend/src/routes/terms/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n redirect,\n useRouter,\n} from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TriangleAlertIcon } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport {\n TermsCheckboxList,\n type TermsConsentsField,\n} from '#frontend/components/terms/terms-checkbox-list.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { LabeledSeparator } from '#frontend/components/ui/labeled-separator.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { OAuthSearchSchema } from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport {\n getTermsQueryOptions,\n type TermsConsentItem,\n termsConsentMutationOptions,\n} from '#frontend/queries/terms.ts';\n\nconst TermsSearchSchema = OAuthSearchSchema.extend({\n redirect: z.string().optional(),\n lang: z.string().optional(),\n mode: z.enum(['normal', 'complete_registration']).optional(),\n registration_token: z.string().optional(),\n});\n\nfunction TermsError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n window.location.href = '/login';\n }}\n />\n );\n}\n\nexport const Route = createFileRoute('/terms/')({\n component: Terms,\n errorComponent: TermsError,\n validateSearch: TermsSearchSchema,\n beforeLoad: async ({ context, search }) => {\n // When mode=complete_registration, user is completing OAuth signup.\n // The registration_token in the URL references a DB record with OAuth data.\n // Skip auth check — backend will validate the token on consent submission.\n if (search.mode === 'complete_registration') {\n return;\n }\n\n // Standard mode: requires authentication\n // Users are redirected here after OAuth login when they need to agree to terms\n if (!context.user) {\n throw redirect({\n to: '/login',\n });\n }\n },\n loaderDeps: ({ search }) => ({\n lang: search.lang,\n }),\n loader: async ({ context, deps }) => {\n const lang = deps.lang ?? context.i18n.language;\n await Promise.all([\n context.queryClient.ensureQueryData(getTermsQueryOptions(lang)),\n context.queryClient.ensureQueryData(appConfigQueryOptions),\n ]);\n },\n});\n\nfunction Terms() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n const lang = search.lang ?? i18n.language;\n const termsQuery = useSuspenseQuery(getTermsQueryOptions(lang));\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n\n // Separate explicit and implicit terms\n const explicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'explicit'),\n [termsQuery.data.terms],\n );\n\n const implicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'implicit'),\n [termsQuery.data.terms],\n );\n\n const hasExplicitTerms = explicitTerms.length > 0;\n\n // Schema only validates explicit terms (implicit are auto-agreed)\n const termsSchema = useMemo(\n () =>\n z.object({\n termsConsents: z.object(\n Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n term.required\n ? z.literal(true, {\n message: t('validation.terms.required'),\n })\n : z.boolean(),\n ]),\n ),\n ),\n }),\n [t, explicitTerms],\n );\n\n type TermsFormValues = TermsConsentsField;\n\n const {\n handleSubmit,\n control,\n setValue,\n formState: { errors },\n } = useForm<TermsFormValues>({\n defaultValues: {\n termsConsents: Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n !!(term.userConsent?.agreed && !term.userConsent.requiresUpdate),\n ]),\n ),\n },\n resolver: standardSchemaResolver(termsSchema),\n mode: 'onChange',\n });\n\n const consentMutation = useMutation({\n ...termsConsentMutationOptions,\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n await queryClient.fetchQuery(getSessionQueryOptions);\n await tick();\n\n // Redirect after successful consent\n if (search.redirect) {\n window.location.href = search.redirect;\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n });\n\n const onSubmit = (values: TermsFormValues) => {\n const explicitConsents = Object.entries(\n values.termsConsents,\n ).map<TermsConsentItem>(([termsId, agreed]) => ({\n termsId,\n agreed,\n consentType: 'explicit',\n }));\n\n const implicitConsents = implicitTerms.map<TermsConsentItem>((term) => ({\n termsId: term.id,\n agreed: true,\n consentType: 'implicit',\n }));\n\n consentMutation.mutate({\n consents: [...explicitConsents, ...implicitConsents],\n ...(search.registration_token && {\n registrationToken: search.registration_token,\n }),\n });\n };\n\n return (\n <AuthLayout width=\"wide\">\n <AuthPageHeader title={t('terms.title')} />\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {implicitNotice && hasExplicitTerms && (\n <LabeledSeparator label={t('terms.additionalOptionalConsent')} />\n )}\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n {/* Explicit terms with checkboxes */}\n {hasExplicitTerms && (\n <TermsCheckboxList\n control={control}\n disabled={consentMutation.isPending}\n errors={errors}\n setValue={setValue}\n terms={explicitTerms}\n />\n )}\n\n {/* Error message */}\n {consentMutation.isError && (\n <Alert icon={TriangleAlertIcon} type=\"error\">\n {t('terms.error.submitFailed')}\n </Alert>\n )}\n\n {/*\n Sticky, like consent: a long list of terms would otherwise push the\n only action off the bottom of the screen.\n */}\n <div className=\"sticky bottom-0 flex gap-tinyrack-sm border-tinyrack-border border-t-tinyrack-default bg-tinyrack-surface/80 py-tinyrack-md backdrop-blur-sm\">\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={consentMutation.isPending}\n loadingLabel={t('terms.submit')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('terms.submit')}\n </TRButton>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"qGA4CA,SAASC,EAAWC,EAA4B,CAC9C,OACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,GAAIA,EACJ,mBAAsB,CACpBE,OAAOC,SAASC,KAAO,QACzB,CAAE,CAAA,CAGR"}
@@ -1,2 +1,2 @@
1
- import{J as e,Z as t,i as n,q as r,s as i}from"./createLucideIcon-BPpAIXR_.js";import{Dn as a,sr as o,t as s}from"./config-v3-oy_ef.js";import{t as c}from"./useMutation-ZLFeJg75.js";import{tt as l}from"./shell-header-bar-C9AB5lT-.js";import{t as u}from"./triangle-alert-CqNn0kPs.js";import{a as d,o as f,r as p}from"./zod-C87S6Tec.js";import{A as m,O as h,S as g,k as _}from"./index-gliHKncz.js";import{t as v}from"./auth-page-header-CIs08f5Y.js";import{t as y}from"./alert-leqyvHOp.js";import{t as b}from"./auth-layout-CoeqNhTT.js";import{t as x}from"./promise-OpBtq8tG.js";import{r as S,t as C}from"./standard-schema-e8gtSzGE.js";import{t as w}from"./terms-checkbox-list-Cw0zB-lO.js";import{t as T}from"./labeled-separator-fnKySKv2.js";var E=t(e()),D=r();function O(){let{t:e,i18n:t}=n(),r=a(),O=o(),k=h.useSearch(),{data:A}=l(s),j=k.lang??t.language,M=l(_(j)),N=A.registration.signup_notice?.[j]??A.registration.signup_notice?.[A.i18n.fallback_language],P=(0,E.useMemo)(()=>M.data.terms.filter(e=>e.consentMode===`explicit`),[M.data.terms]),F=(0,E.useMemo)(()=>M.data.terms.filter(e=>e.consentMode===`implicit`),[M.data.terms]),I=P.length>0,L=(0,E.useMemo)(()=>f({termsConsents:f(Object.fromEntries(P.map(t=>[t.id,t.required?d(!0,{message:e(`validation.terms.required`)}):p()])))}),[e,P]),{handleSubmit:R,control:z,setValue:B,formState:{errors:V}}=S({defaultValues:{termsConsents:Object.fromEntries(P.map(e=>[e.id,!!(e.userConsent?.agreed&&!e.userConsent.requiresUpdate)]))},resolver:C(L),mode:`onChange`}),H=c({...m,onSuccess:async()=>{await O.invalidateQueries({queryKey:g.queryKey}),await O.fetchQuery(g),await x(),k.redirect?window.location.href=k.redirect:r.navigate({to:`/profile`})}});return(0,D.jsxs)(b,{width:`wide`,children:[(0,D.jsx)(v,{title:e(`terms.title`)}),N&&(0,D.jsx)(`div`,{className:`prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!`,dangerouslySetInnerHTML:{__html:N}}),N&&I&&(0,D.jsx)(T,{label:e(`terms.additionalOptionalConsent`)}),(0,D.jsxs)(`form`,{className:`flex flex-col gap-tinyrack-lg`,onSubmit:R(e=>{let t=Object.entries(e.termsConsents).map(([e,t])=>({termsId:e,agreed:t,consentType:`explicit`})),n=F.map(e=>({termsId:e.id,agreed:!0,consentType:`implicit`}));H.mutate({consents:[...t,...n],...k.registration_token&&{registrationToken:k.registration_token}})}),children:[I&&(0,D.jsx)(w,{control:z,disabled:H.isPending,errors:V,setValue:B,terms:P}),H.isError&&(0,D.jsx)(y,{icon:u,type:`error`,children:e(`terms.error.submitFailed`)}),(0,D.jsx)(`div`,{className:`sticky bottom-0 flex gap-tinyrack-sm border-tinyrack-border border-t-tinyrack-default bg-tinyrack-surface/80 py-tinyrack-md backdrop-blur-sm`,children:(0,D.jsx)(i,{className:`w-full`,intent:`primary`,loading:H.isPending,loadingLabel:e(`terms.submit`),type:`submit`,uiSize:`lg`,children:e(`terms.submit`)})})]})]})}export{O as component};
2
- //# sourceMappingURL=terms-COVFmzyl.js.map
1
+ import{J as e,Z as t,i as n,q as r,s as i}from"./createLucideIcon-BPpAIXR_.js";import{On as a,Sr as o,t as s}from"./config-DTQCUlfZ.js";import{t as c}from"./useMutation-BWZ0G4Td.js";import{tt as l}from"./shell-header-bar-CgyNXbPS.js";import{t as u}from"./triangle-alert-CqNn0kPs.js";import{a as d,o as f,r as p}from"./zod-C87S6Tec.js";import{A as m,O as h,S as g,k as _}from"./index-CZ0rWM7l.js";import{t as v}from"./auth-page-header-COvqWINf.js";import{t as y}from"./alert-leqyvHOp.js";import{t as b}from"./auth-layout-DGTylGtX.js";import{t as x}from"./promise-OpBtq8tG.js";import{r as S,t as C}from"./standard-schema-e8gtSzGE.js";import{t as w}from"./terms-checkbox-list-DqbXU4qw.js";import{t as T}from"./labeled-separator-CV7nb0XW.js";var E=t(e()),D=r();function O(){let{t:e,i18n:t}=n(),r=a(),O=o(),k=h.useSearch(),{data:A}=l(s),j=k.lang??t.language,M=l(_(j)),N=A.registration.signup_notice?.[j]??A.registration.signup_notice?.[A.i18n.fallback_language],P=(0,E.useMemo)(()=>M.data.terms.filter(e=>e.consentMode===`explicit`),[M.data.terms]),F=(0,E.useMemo)(()=>M.data.terms.filter(e=>e.consentMode===`implicit`),[M.data.terms]),I=P.length>0,L=(0,E.useMemo)(()=>f({termsConsents:f(Object.fromEntries(P.map(t=>[t.id,t.required?d(!0,{message:e(`validation.terms.required`)}):p()])))}),[e,P]),{handleSubmit:R,control:z,setValue:B,formState:{errors:V}}=S({defaultValues:{termsConsents:Object.fromEntries(P.map(e=>[e.id,!!(e.userConsent?.agreed&&!e.userConsent.requiresUpdate)]))},resolver:C(L),mode:`onChange`}),H=c({...m,onSuccess:async()=>{await O.invalidateQueries({queryKey:g.queryKey}),await O.fetchQuery(g),await x(),k.redirect?window.location.href=k.redirect:r.navigate({to:`/profile`})}});return(0,D.jsxs)(b,{width:`wide`,children:[(0,D.jsx)(v,{title:e(`terms.title`)}),N&&(0,D.jsx)(`div`,{className:`prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!`,dangerouslySetInnerHTML:{__html:N}}),N&&I&&(0,D.jsx)(T,{label:e(`terms.additionalOptionalConsent`)}),(0,D.jsxs)(`form`,{className:`flex flex-col gap-tinyrack-lg`,onSubmit:R(e=>{let t=Object.entries(e.termsConsents).map(([e,t])=>({termsId:e,agreed:t,consentType:`explicit`})),n=F.map(e=>({termsId:e.id,agreed:!0,consentType:`implicit`}));H.mutate({consents:[...t,...n],...k.registration_token&&{registrationToken:k.registration_token}})}),children:[I&&(0,D.jsx)(w,{control:z,disabled:H.isPending,errors:V,setValue:B,terms:P}),H.isError&&(0,D.jsx)(y,{icon:u,type:`error`,children:e(`terms.error.submitFailed`)}),(0,D.jsx)(`div`,{className:`sticky bottom-0 flex gap-tinyrack-sm border-tinyrack-border border-t-tinyrack-default bg-tinyrack-surface/80 py-tinyrack-md backdrop-blur-sm`,children:(0,D.jsx)(i,{className:`w-full`,intent:`primary`,loading:H.isPending,loadingLabel:e(`terms.submit`),type:`submit`,uiSize:`lg`,children:e(`terms.submit`)})})]})]})}export{O as component};
2
+ //# sourceMappingURL=terms-Vgpg5IZK.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terms-COVFmzyl.js","names":["standardSchemaResolver","useMutation","useQueryClient","useSuspenseQuery","useRouter","TRButton","TriangleAlertIcon","useMemo","useForm","useTranslation","z","AuthPageHeader","TermsCheckboxList","Alert","LabeledSeparator","AuthLayout","tick","appConfigQueryOptions","getSessionQueryOptions","getTermsQueryOptions","termsConsentMutationOptions","Route","Terms","t","i18n","router","queryClient","search","useSearch","data","configData","lang","language","termsQuery","implicitNotice","registration","signup_notice","fallback_language","explicitTerms","terms","filter","term","consentMode","implicitTerms","hasExplicitTerms","length","termsSchema","object","termsConsents","Object","fromEntries","map","id","required","literal","message","boolean","TermsFormValues","TermsConsentsField","handleSubmit","control","setValue","formState","errors","defaultValues","userConsent","agreed","requiresUpdate","resolver","mode","consentMutation","onSuccess","invalidateQueries","queryKey","fetchQuery","redirect","window","location","href","navigate","to","onSubmit","values","explicitConsents","entries","TermsConsentItem","termsId","consentType","implicitConsents","mutate","consents","registration_token","registrationToken","__html","isPending","isError","component"],"sources":["../../../frontend/src/routes/terms/index.tsx?tsr-split=component"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n redirect,\n useRouter,\n} from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TriangleAlertIcon } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport {\n TermsCheckboxList,\n type TermsConsentsField,\n} from '#frontend/components/terms/terms-checkbox-list.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { LabeledSeparator } from '#frontend/components/ui/labeled-separator.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { OAuthSearchSchema } from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport {\n getTermsQueryOptions,\n type TermsConsentItem,\n termsConsentMutationOptions,\n} from '#frontend/queries/terms.ts';\n\nconst TermsSearchSchema = OAuthSearchSchema.extend({\n redirect: z.string().optional(),\n lang: z.string().optional(),\n mode: z.enum(['normal', 'complete_registration']).optional(),\n registration_token: z.string().optional(),\n});\n\nfunction TermsError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n window.location.href = '/login';\n }}\n />\n );\n}\n\nexport const Route = createFileRoute('/terms/')({\n component: Terms,\n errorComponent: TermsError,\n validateSearch: TermsSearchSchema,\n beforeLoad: async ({ context, search }) => {\n // When mode=complete_registration, user is completing OAuth signup.\n // The registration_token in the URL references a DB record with OAuth data.\n // Skip auth check — backend will validate the token on consent submission.\n if (search.mode === 'complete_registration') {\n return;\n }\n\n // Standard mode: requires authentication\n // Users are redirected here after OAuth login when they need to agree to terms\n if (!context.user) {\n throw redirect({\n to: '/login',\n });\n }\n },\n loaderDeps: ({ search }) => ({\n lang: search.lang,\n }),\n loader: async ({ context, deps }) => {\n const lang = deps.lang ?? context.i18n.language;\n await Promise.all([\n context.queryClient.ensureQueryData(getTermsQueryOptions(lang)),\n context.queryClient.ensureQueryData(appConfigQueryOptions),\n ]);\n },\n});\n\nfunction Terms() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n const lang = search.lang ?? i18n.language;\n const termsQuery = useSuspenseQuery(getTermsQueryOptions(lang));\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n\n // Separate explicit and implicit terms\n const explicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'explicit'),\n [termsQuery.data.terms],\n );\n\n const implicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'implicit'),\n [termsQuery.data.terms],\n );\n\n const hasExplicitTerms = explicitTerms.length > 0;\n\n // Schema only validates explicit terms (implicit are auto-agreed)\n const termsSchema = useMemo(\n () =>\n z.object({\n termsConsents: z.object(\n Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n term.required\n ? z.literal(true, {\n message: t('validation.terms.required'),\n })\n : z.boolean(),\n ]),\n ),\n ),\n }),\n [t, explicitTerms],\n );\n\n type TermsFormValues = TermsConsentsField;\n\n const {\n handleSubmit,\n control,\n setValue,\n formState: { errors },\n } = useForm<TermsFormValues>({\n defaultValues: {\n termsConsents: Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n !!(term.userConsent?.agreed && !term.userConsent.requiresUpdate),\n ]),\n ),\n },\n resolver: standardSchemaResolver(termsSchema),\n mode: 'onChange',\n });\n\n const consentMutation = useMutation({\n ...termsConsentMutationOptions,\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n await queryClient.fetchQuery(getSessionQueryOptions);\n await tick();\n\n // Redirect after successful consent\n if (search.redirect) {\n window.location.href = search.redirect;\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n });\n\n const onSubmit = (values: TermsFormValues) => {\n const explicitConsents = Object.entries(\n values.termsConsents,\n ).map<TermsConsentItem>(([termsId, agreed]) => ({\n termsId,\n agreed,\n consentType: 'explicit',\n }));\n\n const implicitConsents = implicitTerms.map<TermsConsentItem>((term) => ({\n termsId: term.id,\n agreed: true,\n consentType: 'implicit',\n }));\n\n consentMutation.mutate({\n consents: [...explicitConsents, ...implicitConsents],\n ...(search.registration_token && {\n registrationToken: search.registration_token,\n }),\n });\n };\n\n return (\n <AuthLayout width=\"wide\">\n <AuthPageHeader title={t('terms.title')} />\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {implicitNotice && hasExplicitTerms && (\n <LabeledSeparator label={t('terms.additionalOptionalConsent')} />\n )}\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n {/* Explicit terms with checkboxes */}\n {hasExplicitTerms && (\n <TermsCheckboxList\n control={control}\n disabled={consentMutation.isPending}\n errors={errors}\n setValue={setValue}\n terms={explicitTerms}\n />\n )}\n\n {/* Error message */}\n {consentMutation.isError && (\n <Alert icon={TriangleAlertIcon} type=\"error\">\n {t('terms.error.submitFailed')}\n </Alert>\n )}\n\n {/*\n Sticky, like consent: a long list of terms would otherwise push the\n only action off the bottom of the screen.\n */}\n <div className=\"sticky bottom-0 flex gap-tinyrack-sm border-tinyrack-border border-t-tinyrack-default bg-tinyrack-surface/80 py-tinyrack-md backdrop-blur-sm\">\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={consentMutation.isPending}\n loadingLabel={t('terms.submit')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('terms.submit')}\n </TRButton>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"terms-Vgpg5IZK.js","names":["standardSchemaResolver","useMutation","useQueryClient","useSuspenseQuery","useRouter","TRButton","TriangleAlertIcon","useMemo","useForm","useTranslation","z","AuthPageHeader","TermsCheckboxList","Alert","LabeledSeparator","AuthLayout","tick","appConfigQueryOptions","getSessionQueryOptions","getTermsQueryOptions","termsConsentMutationOptions","Route","Terms","t","i18n","router","queryClient","search","useSearch","data","configData","lang","language","termsQuery","implicitNotice","registration","signup_notice","fallback_language","explicitTerms","terms","filter","term","consentMode","implicitTerms","hasExplicitTerms","length","termsSchema","object","termsConsents","Object","fromEntries","map","id","required","literal","message","boolean","TermsFormValues","TermsConsentsField","handleSubmit","control","setValue","formState","errors","defaultValues","userConsent","agreed","requiresUpdate","resolver","mode","consentMutation","onSuccess","invalidateQueries","queryKey","fetchQuery","redirect","window","location","href","navigate","to","onSubmit","values","explicitConsents","entries","TermsConsentItem","termsId","consentType","implicitConsents","mutate","consents","registration_token","registrationToken","__html","isPending","isError","component"],"sources":["../../../frontend/src/routes/terms/index.tsx?tsr-split=component"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n redirect,\n useRouter,\n} from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TriangleAlertIcon } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport {\n TermsCheckboxList,\n type TermsConsentsField,\n} from '#frontend/components/terms/terms-checkbox-list.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { LabeledSeparator } from '#frontend/components/ui/labeled-separator.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { OAuthSearchSchema } from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport {\n getTermsQueryOptions,\n type TermsConsentItem,\n termsConsentMutationOptions,\n} from '#frontend/queries/terms.ts';\n\nconst TermsSearchSchema = OAuthSearchSchema.extend({\n redirect: z.string().optional(),\n lang: z.string().optional(),\n mode: z.enum(['normal', 'complete_registration']).optional(),\n registration_token: z.string().optional(),\n});\n\nfunction TermsError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n window.location.href = '/login';\n }}\n />\n );\n}\n\nexport const Route = createFileRoute('/terms/')({\n component: Terms,\n errorComponent: TermsError,\n validateSearch: TermsSearchSchema,\n beforeLoad: async ({ context, search }) => {\n // When mode=complete_registration, user is completing OAuth signup.\n // The registration_token in the URL references a DB record with OAuth data.\n // Skip auth check — backend will validate the token on consent submission.\n if (search.mode === 'complete_registration') {\n return;\n }\n\n // Standard mode: requires authentication\n // Users are redirected here after OAuth login when they need to agree to terms\n if (!context.user) {\n throw redirect({\n to: '/login',\n });\n }\n },\n loaderDeps: ({ search }) => ({\n lang: search.lang,\n }),\n loader: async ({ context, deps }) => {\n const lang = deps.lang ?? context.i18n.language;\n await Promise.all([\n context.queryClient.ensureQueryData(getTermsQueryOptions(lang)),\n context.queryClient.ensureQueryData(appConfigQueryOptions),\n ]);\n },\n});\n\nfunction Terms() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n const lang = search.lang ?? i18n.language;\n const termsQuery = useSuspenseQuery(getTermsQueryOptions(lang));\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n\n // Separate explicit and implicit terms\n const explicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'explicit'),\n [termsQuery.data.terms],\n );\n\n const implicitTerms = useMemo(\n () =>\n termsQuery.data.terms.filter((term) => term.consentMode === 'implicit'),\n [termsQuery.data.terms],\n );\n\n const hasExplicitTerms = explicitTerms.length > 0;\n\n // Schema only validates explicit terms (implicit are auto-agreed)\n const termsSchema = useMemo(\n () =>\n z.object({\n termsConsents: z.object(\n Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n term.required\n ? z.literal(true, {\n message: t('validation.terms.required'),\n })\n : z.boolean(),\n ]),\n ),\n ),\n }),\n [t, explicitTerms],\n );\n\n type TermsFormValues = TermsConsentsField;\n\n const {\n handleSubmit,\n control,\n setValue,\n formState: { errors },\n } = useForm<TermsFormValues>({\n defaultValues: {\n termsConsents: Object.fromEntries(\n explicitTerms.map((term) => [\n term.id,\n !!(term.userConsent?.agreed && !term.userConsent.requiresUpdate),\n ]),\n ),\n },\n resolver: standardSchemaResolver(termsSchema),\n mode: 'onChange',\n });\n\n const consentMutation = useMutation({\n ...termsConsentMutationOptions,\n onSuccess: async () => {\n await queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n await queryClient.fetchQuery(getSessionQueryOptions);\n await tick();\n\n // Redirect after successful consent\n if (search.redirect) {\n window.location.href = search.redirect;\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n });\n\n const onSubmit = (values: TermsFormValues) => {\n const explicitConsents = Object.entries(\n values.termsConsents,\n ).map<TermsConsentItem>(([termsId, agreed]) => ({\n termsId,\n agreed,\n consentType: 'explicit',\n }));\n\n const implicitConsents = implicitTerms.map<TermsConsentItem>((term) => ({\n termsId: term.id,\n agreed: true,\n consentType: 'implicit',\n }));\n\n consentMutation.mutate({\n consents: [...explicitConsents, ...implicitConsents],\n ...(search.registration_token && {\n registrationToken: search.registration_token,\n }),\n });\n };\n\n return (\n <AuthLayout width=\"wide\">\n <AuthPageHeader title={t('terms.title')} />\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {implicitNotice && hasExplicitTerms && (\n <LabeledSeparator label={t('terms.additionalOptionalConsent')} />\n )}\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n {/* Explicit terms with checkboxes */}\n {hasExplicitTerms && (\n <TermsCheckboxList\n control={control}\n disabled={consentMutation.isPending}\n errors={errors}\n setValue={setValue}\n terms={explicitTerms}\n />\n )}\n\n {/* Error message */}\n {consentMutation.isError && (\n <Alert icon={TriangleAlertIcon} type=\"error\">\n {t('terms.error.submitFailed')}\n </Alert>\n )}\n\n {/*\n Sticky, like consent: a long list of terms would otherwise push the\n only action off the bottom of the screen.\n */}\n <div className=\"sticky bottom-0 flex gap-tinyrack-sm border-tinyrack-border border-t-tinyrack-default bg-tinyrack-surface/80 py-tinyrack-md backdrop-blur-sm\">\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={consentMutation.isPending}\n loadingLabel={t('terms.submit')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('terms.submit')}\n </TRButton>\n </div>\n </form>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{J as e,Z as t,i as n,q as r,s as i}from"./createLucideIcon-BPpAIXR_.js";import{t as a}from"./badge-CsOfUji0.js";import{t as o}from"./checkbox-CJrRV8S1.js";import{t as s}from"./modal-sCM_zT82.js";import{t as c}from"./link-C-WdTj3F.js";import{i as l,n as u}from"./standard-schema-e8gtSzGE.js";var d=t(e(),1),f=r();function p({isOpen:e,onClose:t,title:n,content:r}){return(0,f.jsx)(s,{isOpen:e,onClose:t,size:`lg`,title:n,children:(0,f.jsx)(`div`,{className:`prose prose-sm mt-tinyrack-md max-h-dvh overflow-y-auto`,dangerouslySetInnerHTML:{__html:r}})})}function m({terms:e,control:t,setValue:r,errors:s,disabled:m=!1}){let{t:g}=n(),[_,v]=(0,d.useState)(null),y=l({control:t,name:`termsConsents`}),b=t=>{for(let n of e)r(`termsConsents.${n.id}`,t,{shouldValidate:!0})},x=e.every(e=>y?.[e.id]),S=e.some(e=>!e.required),C=s?.termsConsents,w=e=>e.title===e.id?h(e.id):e.title;return(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`space-y-tinyrack-xs`,children:[e.length>1&&(0,f.jsxs)(`div`,{className:`flex cursor-pointer items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]`,children:[(0,f.jsx)(o.Root,{"aria-labelledby":`terms-all-label`,checked:x,"data-testid":`terms-checkbox`,disabled:m,onCheckedChange:b,children:(0,f.jsx)(o.Indicator,{})}),(0,f.jsx)(`span`,{className:`font-tinyrack-medium text-tinyrack-xs`,id:`terms-all-label`,children:g(`terms.agreeAll`)}),S&&(0,f.jsx)(`span`,{className:`text-tinyrack-text-muted text-tinyrack-xs`,children:g(`terms.agreeAllOptionalIncluded`)})]}),e.map(e=>(0,f.jsx)(u,{control:t,name:`termsConsents.${e.id}`,render:({field:t})=>(0,f.jsxs)(`div`,{children:[(0,f.jsxs)(`div`,{className:`flex items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]`,children:[(0,f.jsxs)(`div`,{className:`flex min-w-0 flex-1 items-center gap-tinyrack-sm`,children:[(0,f.jsx)(o.Root,{"aria-labelledby":`term-label-${e.id}`,checked:t.value===!0,"data-testid":`terms-checkbox`,disabled:m,onCheckedChange:e=>t.onChange(e===!0),children:(0,f.jsx)(o.Indicator,{})}),(0,f.jsx)(`span`,{className:`truncate text-tinyrack-xs`,id:`term-label-${e.id}`,children:w(e)}),(0,f.jsx)(a,{"data-testid":e.required?`terms-badge-required`:`terms-badge-optional`,uiSize:`md`,variant:e.required?`danger`:`neutral`,children:e.required?g(`terms.required`):g(`terms.optional`)})]}),e.type===`link`&&e.content&&(0,f.jsx)(c,{"aria-label":g(`terms.viewSpecific`,{title:w(e)}),className:`shrink-0 text-tinyrack-xs`,href:e.content,rel:`noopener noreferrer`,target:`_blank`,children:g(`terms.view`)}),e.type===`text`&&e.content&&(0,f.jsx)(i,{appearance:`ghost`,"aria-label":g(`terms.viewSpecific`,{title:w(e)}),className:`shrink-0 text-tinyrack-xs`,onClick:()=>v(e),type:`button`,uiSize:`sm`,children:g(`terms.view`)})]}),e.userConsent?.requiresUpdate&&(0,f.jsx)(`p`,{className:`ml-tinyrack-xl text-tinyrack-warning text-tinyrack-xs`,children:g(`terms.versionUpdated`)}),C?.[e.id]&&(0,f.jsx)(`p`,{className:`ml-tinyrack-xl text-tinyrack-danger text-tinyrack-xs`,"data-testid":`terms-field-error`,children:C[e.id]?.message})]})},e.id))]}),(0,f.jsx)(p,{content:_?.content??``,isOpen:_!==null,onClose:()=>v(null),title:_?.title??``})]})}function h(e){return e.replaceAll(`-`,` `).replaceAll(`_`,` `).replace(/([a-zA-Z])(\d)/g,`$1 $2`).split(` `).filter(e=>e.length>0).map(e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`).join(` `)}export{m as t};
2
- //# sourceMappingURL=terms-checkbox-list-Cw0zB-lO.js.map
1
+ import{J as e,Z as t,i as n,q as r,s as i}from"./createLucideIcon-BPpAIXR_.js";import{t as a}from"./badge-CsOfUji0.js";import{t as o}from"./checkbox-CFVDS3zJ.js";import{t as s}from"./modal-BV3gpI-p.js";import{t as c}from"./link-C-WdTj3F.js";import{i as l,n as u}from"./standard-schema-e8gtSzGE.js";var d=t(e(),1),f=r();function p({isOpen:e,onClose:t,title:n,content:r}){return(0,f.jsx)(s,{isOpen:e,onClose:t,size:`lg`,title:n,children:(0,f.jsx)(`div`,{className:`prose prose-sm mt-tinyrack-md max-h-dvh overflow-y-auto`,dangerouslySetInnerHTML:{__html:r}})})}function m({terms:e,control:t,setValue:r,errors:s,disabled:m=!1}){let{t:g}=n(),[_,v]=(0,d.useState)(null),y=l({control:t,name:`termsConsents`}),b=t=>{for(let n of e)r(`termsConsents.${n.id}`,t,{shouldValidate:!0})},x=e.every(e=>y?.[e.id]),S=e.some(e=>!e.required),C=s?.termsConsents,w=e=>e.title===e.id?h(e.id):e.title;return(0,f.jsxs)(f.Fragment,{children:[(0,f.jsxs)(`div`,{className:`space-y-tinyrack-xs`,children:[e.length>1&&(0,f.jsxs)(`div`,{className:`flex cursor-pointer items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]`,children:[(0,f.jsx)(o.Root,{"aria-labelledby":`terms-all-label`,checked:x,"data-testid":`terms-checkbox`,disabled:m,onCheckedChange:b,children:(0,f.jsx)(o.Indicator,{})}),(0,f.jsx)(`span`,{className:`font-tinyrack-medium text-tinyrack-xs`,id:`terms-all-label`,children:g(`terms.agreeAll`)}),S&&(0,f.jsx)(`span`,{className:`text-tinyrack-text-muted text-tinyrack-xs`,children:g(`terms.agreeAllOptionalIncluded`)})]}),e.map(e=>(0,f.jsx)(u,{control:t,name:`termsConsents.${e.id}`,render:({field:t})=>(0,f.jsxs)(`div`,{children:[(0,f.jsxs)(`div`,{className:`flex items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]`,children:[(0,f.jsxs)(`div`,{className:`flex min-w-0 flex-1 items-center gap-tinyrack-sm`,children:[(0,f.jsx)(o.Root,{"aria-labelledby":`term-label-${e.id}`,checked:t.value===!0,"data-testid":`terms-checkbox`,disabled:m,onCheckedChange:e=>t.onChange(e===!0),children:(0,f.jsx)(o.Indicator,{})}),(0,f.jsx)(`span`,{className:`truncate text-tinyrack-xs`,id:`term-label-${e.id}`,children:w(e)}),(0,f.jsx)(a,{"data-testid":e.required?`terms-badge-required`:`terms-badge-optional`,uiSize:`md`,variant:e.required?`danger`:`neutral`,children:e.required?g(`terms.required`):g(`terms.optional`)})]}),e.type===`link`&&e.content&&(0,f.jsx)(c,{"aria-label":g(`terms.viewSpecific`,{title:w(e)}),className:`shrink-0 text-tinyrack-xs`,href:e.content,rel:`noopener noreferrer`,target:`_blank`,children:g(`terms.view`)}),e.type===`text`&&e.content&&(0,f.jsx)(i,{appearance:`ghost`,"aria-label":g(`terms.viewSpecific`,{title:w(e)}),className:`shrink-0 text-tinyrack-xs`,onClick:()=>v(e),type:`button`,uiSize:`sm`,children:g(`terms.view`)})]}),e.userConsent?.requiresUpdate&&(0,f.jsx)(`p`,{className:`ml-tinyrack-xl text-tinyrack-warning-foreground text-tinyrack-xs`,children:g(`terms.versionUpdated`)}),C?.[e.id]&&(0,f.jsx)(`p`,{className:`ml-tinyrack-xl text-tinyrack-danger-foreground text-tinyrack-xs`,"data-testid":`terms-field-error`,children:C[e.id]?.message})]})},e.id))]}),(0,f.jsx)(p,{content:_?.content??``,isOpen:_!==null,onClose:()=>v(null),title:_?.title??``})]})}function h(e){return e.replaceAll(`-`,` `).replaceAll(`_`,` `).replace(/([a-zA-Z])(\d)/g,`$1 $2`).split(` `).filter(e=>e.length>0).map(e=>`${e.charAt(0).toUpperCase()}${e.slice(1)}`).join(` `)}export{m as t};
2
+ //# sourceMappingURL=terms-checkbox-list-DqbXU4qw.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terms-checkbox-list-Cw0zB-lO.js","names":[],"sources":["../../../frontend/src/components/terms/terms-content-modal.tsx","../../../frontend/src/components/terms/terms-checkbox-list.tsx"],"sourcesContent":["import { Modal } from '#frontend/components/ui/modal.tsx';\n\ntype TermsContentModalProps = {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n content: string;\n};\n\nexport function TermsContentModal({\n isOpen,\n onClose,\n title,\n content,\n}: TermsContentModalProps) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} size=\"lg\" title={title}>\n <div\n className=\"prose prose-sm mt-tinyrack-md max-h-dvh overflow-y-auto\"\n dangerouslySetInnerHTML={{ __html: content }}\n />\n </Modal>\n );\n}\n","import { TRBadge } from '@tinyrack/ui/components/badge';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRCheckbox } from '@tinyrack/ui/components/checkbox';\nimport { TRLink } from '@tinyrack/ui/components/link';\nimport { useState } from 'react';\nimport {\n type Control,\n Controller,\n type FieldErrors,\n type FieldValues,\n type Path,\n type PathValue,\n type UseFormSetValue,\n useWatch,\n} from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\n\nimport type { TermItem } from '#frontend/queries/terms.ts';\nimport { TermsContentModal } from './terms-content-modal.tsx';\n\nexport type TermsConsentsField = {\n termsConsents: Record<string, boolean>;\n};\n\ntype TermsCheckboxListProps<T extends FieldValues & TermsConsentsField> = {\n terms: TermItem[];\n control: Control<T>;\n setValue: UseFormSetValue<T>;\n errors?: FieldErrors<T>;\n disabled?: boolean;\n};\n\nexport function TermsCheckboxList<T extends FieldValues & TermsConsentsField>({\n terms,\n control,\n setValue,\n errors,\n disabled = false,\n}: TermsCheckboxListProps<T>) {\n const { t } = useTranslation();\n const [modalTerm, setModalTerm] = useState<TermItem | null>(null);\n\n const termsConsents = useWatch({\n control,\n name: 'termsConsents' as Path<T>,\n }) as Record<string, boolean> | undefined;\n\n const handleAllChange = (checked: boolean) => {\n for (const term of terms) {\n setValue(\n `termsConsents.${term.id}` as Path<T>,\n checked as PathValue<T, Path<T>>,\n { shouldValidate: true },\n );\n }\n };\n\n const allChecked = terms.every((term) => termsConsents?.[term.id]);\n\n const hasOptionalTerms = terms.some((term) => !term.required);\n\n const termsConsentsErrors = errors?.termsConsents as\n | Record<string, { message?: string }>\n | undefined;\n\n const getTermTitle = (term: TermItem) => {\n if (term.title !== term.id) {\n return term.title;\n }\n\n return humanizeTermId(term.id);\n };\n\n return (\n <>\n <div className=\"space-y-tinyrack-xs\">\n {terms.length > 1 && (\n <div className=\"flex cursor-pointer items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]\">\n <TRCheckbox.Root\n aria-labelledby=\"terms-all-label\"\n checked={allChecked}\n data-testid=\"terms-checkbox\"\n disabled={disabled}\n onCheckedChange={handleAllChange}\n >\n <TRCheckbox.Indicator />\n </TRCheckbox.Root>\n <span\n className=\"font-tinyrack-medium text-tinyrack-xs\"\n id=\"terms-all-label\"\n >\n {t('terms.agreeAll')}\n </span>\n {hasOptionalTerms && (\n <span className=\"text-tinyrack-text-muted text-tinyrack-xs\">\n {t('terms.agreeAllOptionalIncluded')}\n </span>\n )}\n </div>\n )}\n\n {terms.map((term) => (\n <Controller\n control={control}\n key={term.id}\n name={`termsConsents.${term.id}` as Path<T>}\n render={({ field }) => (\n <div>\n <div className=\"flex items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]\">\n <div className=\"flex min-w-0 flex-1 items-center gap-tinyrack-sm\">\n <TRCheckbox.Root\n aria-labelledby={`term-label-${term.id}`}\n checked={field.value === true}\n data-testid=\"terms-checkbox\"\n disabled={disabled}\n onCheckedChange={(checked) =>\n field.onChange(checked === true)\n }\n >\n <TRCheckbox.Indicator />\n </TRCheckbox.Root>\n <span\n className=\"truncate text-tinyrack-xs\"\n id={`term-label-${term.id}`}\n >\n {getTermTitle(term)}\n </span>\n <TRBadge\n data-testid={\n term.required\n ? 'terms-badge-required'\n : 'terms-badge-optional'\n }\n uiSize=\"md\"\n variant={term.required ? 'danger' : 'neutral'}\n >\n {term.required\n ? t('terms.required')\n : t('terms.optional')}\n </TRBadge>\n </div>\n {term.type === 'link' && term.content && (\n <TRLink\n aria-label={t('terms.viewSpecific', {\n title: getTermTitle(term),\n })}\n className=\"shrink-0 text-tinyrack-xs\"\n href={term.content}\n rel=\"noopener noreferrer\"\n target=\"_blank\"\n >\n {t('terms.view')}\n </TRLink>\n )}\n {term.type === 'text' && term.content && (\n <TRButton\n appearance=\"ghost\"\n aria-label={t('terms.viewSpecific', {\n title: getTermTitle(term),\n })}\n className=\"shrink-0 text-tinyrack-xs\"\n onClick={() => setModalTerm(term)}\n type=\"button\"\n uiSize=\"sm\"\n >\n {t('terms.view')}\n </TRButton>\n )}\n </div>\n {term.userConsent?.requiresUpdate && (\n <p className=\"ml-tinyrack-xl text-tinyrack-warning text-tinyrack-xs\">\n {t('terms.versionUpdated')}\n </p>\n )}\n {termsConsentsErrors?.[term.id] && (\n <p\n className=\"ml-tinyrack-xl text-tinyrack-danger text-tinyrack-xs\"\n data-testid=\"terms-field-error\"\n >\n {termsConsentsErrors[term.id]?.message}\n </p>\n )}\n </div>\n )}\n />\n ))}\n </div>\n\n <TermsContentModal\n content={modalTerm?.content ?? ''}\n isOpen={modalTerm !== null}\n onClose={() => setModalTerm(null)}\n title={modalTerm?.title ?? ''}\n />\n </>\n );\n}\n\nfunction humanizeTermId(id: string): string {\n const words = id\n .replaceAll('-', ' ')\n .replaceAll('_', ' ')\n .replace(/([a-zA-Z])(\\d)/g, '$1 $2')\n .split(' ')\n .filter((word) => word.length > 0);\n\n return words\n .map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)\n .join(' ');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"terms-checkbox-list-DqbXU4qw.js","names":[],"sources":["../../../frontend/src/components/terms/terms-content-modal.tsx","../../../frontend/src/components/terms/terms-checkbox-list.tsx"],"sourcesContent":["import { Modal } from '#frontend/components/ui/modal.tsx';\n\ntype TermsContentModalProps = {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n content: string;\n};\n\nexport function TermsContentModal({\n isOpen,\n onClose,\n title,\n content,\n}: TermsContentModalProps) {\n return (\n <Modal isOpen={isOpen} onClose={onClose} size=\"lg\" title={title}>\n <div\n className=\"prose prose-sm mt-tinyrack-md max-h-dvh overflow-y-auto\"\n dangerouslySetInnerHTML={{ __html: content }}\n />\n </Modal>\n );\n}\n","import { TRBadge } from '@tinyrack/ui/components/badge';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRCheckbox } from '@tinyrack/ui/components/checkbox';\nimport { TRLink } from '@tinyrack/ui/components/link';\nimport { useState } from 'react';\nimport {\n type Control,\n Controller,\n type FieldErrors,\n type FieldValues,\n type Path,\n type PathValue,\n type UseFormSetValue,\n useWatch,\n} from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\n\nimport type { TermItem } from '#frontend/queries/terms.ts';\nimport { TermsContentModal } from './terms-content-modal.tsx';\n\nexport type TermsConsentsField = {\n termsConsents: Record<string, boolean>;\n};\n\ntype TermsCheckboxListProps<T extends FieldValues & TermsConsentsField> = {\n terms: TermItem[];\n control: Control<T>;\n setValue: UseFormSetValue<T>;\n errors?: FieldErrors<T>;\n disabled?: boolean;\n};\n\nexport function TermsCheckboxList<T extends FieldValues & TermsConsentsField>({\n terms,\n control,\n setValue,\n errors,\n disabled = false,\n}: TermsCheckboxListProps<T>) {\n const { t } = useTranslation();\n const [modalTerm, setModalTerm] = useState<TermItem | null>(null);\n\n const termsConsents = useWatch({\n control,\n name: 'termsConsents' as Path<T>,\n }) as Record<string, boolean> | undefined;\n\n const handleAllChange = (checked: boolean) => {\n for (const term of terms) {\n setValue(\n `termsConsents.${term.id}` as Path<T>,\n checked as PathValue<T, Path<T>>,\n { shouldValidate: true },\n );\n }\n };\n\n const allChecked = terms.every((term) => termsConsents?.[term.id]);\n\n const hasOptionalTerms = terms.some((term) => !term.required);\n\n const termsConsentsErrors = errors?.termsConsents as\n | Record<string, { message?: string }>\n | undefined;\n\n const getTermTitle = (term: TermItem) => {\n if (term.title !== term.id) {\n return term.title;\n }\n\n return humanizeTermId(term.id);\n };\n\n return (\n <>\n <div className=\"space-y-tinyrack-xs\">\n {terms.length > 1 && (\n <div className=\"flex cursor-pointer items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]\">\n <TRCheckbox.Root\n aria-labelledby=\"terms-all-label\"\n checked={allChecked}\n data-testid=\"terms-checkbox\"\n disabled={disabled}\n onCheckedChange={handleAllChange}\n >\n <TRCheckbox.Indicator />\n </TRCheckbox.Root>\n <span\n className=\"font-tinyrack-medium text-tinyrack-xs\"\n id=\"terms-all-label\"\n >\n {t('terms.agreeAll')}\n </span>\n {hasOptionalTerms && (\n <span className=\"text-tinyrack-text-muted text-tinyrack-xs\">\n {t('terms.agreeAllOptionalIncluded')}\n </span>\n )}\n </div>\n )}\n\n {terms.map((term) => (\n <Controller\n control={control}\n key={term.id}\n name={`termsConsents.${term.id}` as Path<T>}\n render={({ field }) => (\n <div>\n <div className=\"flex items-center gap-tinyrack-sm py-[calc(var(--tinyrack-space-3xs)*2)]\">\n <div className=\"flex min-w-0 flex-1 items-center gap-tinyrack-sm\">\n <TRCheckbox.Root\n aria-labelledby={`term-label-${term.id}`}\n checked={field.value === true}\n data-testid=\"terms-checkbox\"\n disabled={disabled}\n onCheckedChange={(checked) =>\n field.onChange(checked === true)\n }\n >\n <TRCheckbox.Indicator />\n </TRCheckbox.Root>\n <span\n className=\"truncate text-tinyrack-xs\"\n id={`term-label-${term.id}`}\n >\n {getTermTitle(term)}\n </span>\n <TRBadge\n data-testid={\n term.required\n ? 'terms-badge-required'\n : 'terms-badge-optional'\n }\n uiSize=\"md\"\n variant={term.required ? 'danger' : 'neutral'}\n >\n {term.required\n ? t('terms.required')\n : t('terms.optional')}\n </TRBadge>\n </div>\n {term.type === 'link' && term.content && (\n <TRLink\n aria-label={t('terms.viewSpecific', {\n title: getTermTitle(term),\n })}\n className=\"shrink-0 text-tinyrack-xs\"\n href={term.content}\n rel=\"noopener noreferrer\"\n target=\"_blank\"\n >\n {t('terms.view')}\n </TRLink>\n )}\n {term.type === 'text' && term.content && (\n <TRButton\n appearance=\"ghost\"\n aria-label={t('terms.viewSpecific', {\n title: getTermTitle(term),\n })}\n className=\"shrink-0 text-tinyrack-xs\"\n onClick={() => setModalTerm(term)}\n type=\"button\"\n uiSize=\"sm\"\n >\n {t('terms.view')}\n </TRButton>\n )}\n </div>\n {term.userConsent?.requiresUpdate && (\n <p className=\"ml-tinyrack-xl text-tinyrack-warning-foreground text-tinyrack-xs\">\n {t('terms.versionUpdated')}\n </p>\n )}\n {termsConsentsErrors?.[term.id] && (\n <p\n className=\"ml-tinyrack-xl text-tinyrack-danger-foreground text-tinyrack-xs\"\n data-testid=\"terms-field-error\"\n >\n {termsConsentsErrors[term.id]?.message}\n </p>\n )}\n </div>\n )}\n />\n ))}\n </div>\n\n <TermsContentModal\n content={modalTerm?.content ?? ''}\n isOpen={modalTerm !== null}\n onClose={() => setModalTerm(null)}\n title={modalTerm?.title ?? ''}\n />\n </>\n );\n}\n\nfunction humanizeTermId(id: string): string {\n const words = id\n .replaceAll('-', ' ')\n .replaceAll('_', ' ')\n .replace(/([a-zA-Z])(\\d)/g, '$1 $2')\n .split(' ')\n .filter((word) => word.length > 0);\n\n return words\n .map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)\n .join(' ');\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{i as e,r as t}from"./config-v3-oy_ef.js";import{t as n}from"./mutationOptions-CrswitPs.js";var r=n({mutationFn:async()=>e(await t.api.user.totp.setup.$post())}),i=n({mutationFn:async n=>e(await t.api.user.totp.verify.$post({json:n}))}),a=n({mutationFn:async()=>e(await t.api.user.totp.confirm.$post({json:{}}))}),o=n({mutationFn:async n=>e(await t.api.user.totp.$delete({json:n}))}),s=n({mutationFn:async n=>e(await t.api.user.totp.recovery.regenerate.$post({json:n}))}),c=n({mutationFn:async n=>e(await t.api.auth.totp.verify.$post({json:n}))}),l=n({mutationFn:async n=>e(await t.api.auth.totp.recovery.verify.$post({json:n}))});export{l as a,r as i,o as n,c as o,s as r,i as s,a as t};
2
- //# sourceMappingURL=totp-BfCm6IWl.js.map
1
+ import{i as e,r as t}from"./config-DTQCUlfZ.js";import{t as n}from"./mutationOptions-DyYFUbyn.js";var r=n({mutationFn:async()=>e(await t.api.user.totp.setup.$post())}),i=n({mutationFn:async n=>e(await t.api.user.totp.verify.$post({json:n}))}),a=n({mutationFn:async()=>e(await t.api.user.totp.confirm.$post({json:{}}))}),o=n({mutationFn:async n=>e(await t.api.user.totp.$delete({json:n}))}),s=n({mutationFn:async n=>e(await t.api.user.totp.recovery.regenerate.$post({json:n}))}),c=n({mutationFn:async n=>e(await t.api.auth.totp.verify.$post({json:n}))}),l=n({mutationFn:async n=>e(await t.api.auth.totp.recovery.verify.$post({json:n}))});export{l as a,r as i,o as n,c as o,s as r,i as s,a as t};
2
+ //# sourceMappingURL=totp-BNZ904Zm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"totp-BfCm6IWl.js","names":[],"sources":["../../../frontend/src/queries/totp.ts"],"sourcesContent":["import { mutationOptions } from '@tanstack/react-query';\nimport type { InferRequestType, InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\n\nexport type TotpSetupResponse = InferResponseType<\n (typeof client.api.user.totp.setup)['$post'],\n 200\n>;\n\nexport type TotpSetupVerifyResponse = InferResponseType<\n (typeof client.api.user.totp.verify)['$post'],\n 200\n>;\n\nexport type TotpConfirmResponse = InferResponseType<\n (typeof client.api.user.totp.confirm)['$post'],\n 200\n>;\n\nexport type RegenerateTotpRecoveryCodesResponse = InferResponseType<\n (typeof client.api.user.totp.recovery.regenerate)['$post'],\n 200\n>;\n\nexport type TotpLoginVerifyResponse = InferResponseType<\n (typeof client.api.auth.totp.verify)['$post'],\n 200\n>;\n\n/**\n * Start TOTP setup - generates secret and QR code\n */\nexport const startTotpSetupMutationOptions = mutationOptions({\n mutationFn: async () => {\n const res = await client.api.user.totp.setup.$post();\n return jsonOk(res);\n },\n});\n\n/**\n * Verify TOTP code during setup (returns recovery codes)\n */\nexport type VerifyTotpParams = InferRequestType<\n (typeof client.api.user.totp.verify)['$post']\n>['json'];\n\nexport const verifyTotpMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyTotpParams) => {\n const res = await client.api.user.totp.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Confirm TOTP setup after user acknowledges recovery\n * codes\n */\nexport const confirmTotpSetupMutationOptions = mutationOptions({\n mutationFn: async () => {\n const res = await client.api.user.totp.confirm.$post({\n json: {},\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Disable TOTP authentication\n */\nexport type DisableTotpParams = InferRequestType<\n (typeof client.api.user.totp)['$delete']\n>['json'];\n\nexport const disableTotpMutationOptions = mutationOptions({\n mutationFn: async (values: DisableTotpParams) => {\n const res = await client.api.user.totp.$delete({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\nexport type RegenerateTotpRecoveryCodesParams = InferRequestType<\n (typeof client.api.user.totp.recovery.regenerate)['$post']\n>['json'];\n\nexport const regenerateTotpRecoveryCodesMutationOptions = mutationOptions({\n mutationFn: async (values: RegenerateTotpRecoveryCodesParams) => {\n const res = await client.api.user.totp.recovery.regenerate.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Verify TOTP code during login (complete 2FA login)\n */\nexport type VerifyTotpLoginParams = InferRequestType<\n (typeof client.api.auth.totp.verify)['$post']\n>['json'];\n\nexport const verifyTotpLoginMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyTotpLoginParams) => {\n const res = await client.api.auth.totp.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Verify recovery code during login (complete 2FA login)\n */\nexport type VerifyRecoveryCodeParams = InferRequestType<\n (typeof client.api.auth.totp.recovery.verify)['$post']\n>['json'];\n\nexport type VerifyRecoveryCodeResponse = InferResponseType<\n (typeof client.api.auth.totp.recovery.verify)['$post'],\n 200\n>;\n\nexport const verifyRecoveryCodeMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyRecoveryCodeParams) => {\n const res = await client.api.auth.totp.recovery.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n"],"mappings":"kGAgCA,IAAa,EAAgC,EAAgB,CAC3D,WAAY,SAEH,EAAO,MADI,EAAO,IAAI,KAAK,KAAK,MAAM,MAAM,CAClC,CAErB,CAAC,EASY,EAA4B,EAAgB,CACvD,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,OAAO,MAAM,CAClD,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EAMY,EAAkC,EAAgB,CAC7D,WAAY,SAIH,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,QAAQ,MAAM,CACnD,KAAM,CAAC,CACT,CAAC,CACgB,CAErB,CAAC,EASY,EAA6B,EAAgB,CACxD,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,QAAQ,CAC7C,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EAMY,EAA6C,EAAgB,CACxE,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,SAAS,WAAW,MAAM,CAC/D,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EASY,EAAiC,EAAgB,CAC5D,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,OAAO,MAAM,CAClD,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EAcY,EAAoC,EAAgB,CAC/D,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,SAAS,OAAO,MAAM,CAC3D,KAAM,CACR,CAAC,CACgB,CAErB,CAAC"}
1
+ {"version":3,"file":"totp-BNZ904Zm.js","names":[],"sources":["../../../frontend/src/queries/totp.ts"],"sourcesContent":["import { mutationOptions } from '@tanstack/react-query';\nimport type { InferRequestType, InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\n\nexport type TotpSetupResponse = InferResponseType<\n (typeof client.api.user.totp.setup)['$post'],\n 200\n>;\n\nexport type TotpSetupVerifyResponse = InferResponseType<\n (typeof client.api.user.totp.verify)['$post'],\n 200\n>;\n\nexport type TotpConfirmResponse = InferResponseType<\n (typeof client.api.user.totp.confirm)['$post'],\n 200\n>;\n\nexport type RegenerateTotpRecoveryCodesResponse = InferResponseType<\n (typeof client.api.user.totp.recovery.regenerate)['$post'],\n 200\n>;\n\nexport type TotpLoginVerifyResponse = InferResponseType<\n (typeof client.api.auth.totp.verify)['$post'],\n 200\n>;\n\n/**\n * Start TOTP setup - generates secret and QR code\n */\nexport const startTotpSetupMutationOptions = mutationOptions({\n mutationFn: async () => {\n const res = await client.api.user.totp.setup.$post();\n return jsonOk(res);\n },\n});\n\n/**\n * Verify TOTP code during setup (returns recovery codes)\n */\nexport type VerifyTotpParams = InferRequestType<\n (typeof client.api.user.totp.verify)['$post']\n>['json'];\n\nexport const verifyTotpMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyTotpParams) => {\n const res = await client.api.user.totp.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Confirm TOTP setup after user acknowledges recovery\n * codes\n */\nexport const confirmTotpSetupMutationOptions = mutationOptions({\n mutationFn: async () => {\n const res = await client.api.user.totp.confirm.$post({\n json: {},\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Disable TOTP authentication\n */\nexport type DisableTotpParams = InferRequestType<\n (typeof client.api.user.totp)['$delete']\n>['json'];\n\nexport const disableTotpMutationOptions = mutationOptions({\n mutationFn: async (values: DisableTotpParams) => {\n const res = await client.api.user.totp.$delete({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\nexport type RegenerateTotpRecoveryCodesParams = InferRequestType<\n (typeof client.api.user.totp.recovery.regenerate)['$post']\n>['json'];\n\nexport const regenerateTotpRecoveryCodesMutationOptions = mutationOptions({\n mutationFn: async (values: RegenerateTotpRecoveryCodesParams) => {\n const res = await client.api.user.totp.recovery.regenerate.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Verify TOTP code during login (complete 2FA login)\n */\nexport type VerifyTotpLoginParams = InferRequestType<\n (typeof client.api.auth.totp.verify)['$post']\n>['json'];\n\nexport const verifyTotpLoginMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyTotpLoginParams) => {\n const res = await client.api.auth.totp.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\n/**\n * Verify recovery code during login (complete 2FA login)\n */\nexport type VerifyRecoveryCodeParams = InferRequestType<\n (typeof client.api.auth.totp.recovery.verify)['$post']\n>['json'];\n\nexport type VerifyRecoveryCodeResponse = InferResponseType<\n (typeof client.api.auth.totp.recovery.verify)['$post'],\n 200\n>;\n\nexport const verifyRecoveryCodeMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyRecoveryCodeParams) => {\n const res = await client.api.auth.totp.recovery.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n"],"mappings":"kGAgCA,IAAa,EAAgC,EAAgB,CAC3D,WAAY,SAEH,EAAO,MADI,EAAO,IAAI,KAAK,KAAK,MAAM,MAAM,CAClC,CAErB,CAAC,EASY,EAA4B,EAAgB,CACvD,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,OAAO,MAAM,CAClD,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EAMY,EAAkC,EAAgB,CAC7D,WAAY,SAIH,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,QAAQ,MAAM,CACnD,KAAM,CAAC,CACT,CAAC,CACgB,CAErB,CAAC,EASY,EAA6B,EAAgB,CACxD,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,QAAQ,CAC7C,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EAMY,EAA6C,EAAgB,CACxE,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,SAAS,WAAW,MAAM,CAC/D,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EASY,EAAiC,EAAgB,CAC5D,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,OAAO,MAAM,CAClD,KAAM,CACR,CAAC,CACgB,CAErB,CAAC,EAcY,EAAoC,EAAgB,CAC/D,WAAY,KAAO,IAIV,EAAO,MAHI,EAAO,IAAI,KAAK,KAAK,SAAS,OAAO,MAAM,CAC3D,KAAM,CACR,CAAC,CACgB,CAErB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import{J as e,Z as t,i as n,q as r,r as i,s as a}from"./createLucideIcon-BPpAIXR_.js";import{On as o,Sn as s,Sr as c}from"./config-DTQCUlfZ.js";import{t as l}from"./useMutation-BWZ0G4Td.js";import{c as u,o as d}from"./zod-C87S6Tec.js";import{F as f,N as p,R as m,S as h,h as g,j as _}from"./index-CZ0rWM7l.js";import{t as v}from"./auth-page-header-COvqWINf.js";import{t as y}from"./alert-leqyvHOp.js";import{t as b}from"./auth-layout-DGTylGtX.js";import{t as x}from"./promise-OpBtq8tG.js";import{n as S,t as C}from"./auth-footer-Cz5ivsDc.js";import{r as w,t as T}from"./standard-schema-e8gtSzGE.js";import{o as E}from"./totp-BNZ904Zm.js";import{t as D}from"./pin-input-BGOLfkwL.js";import{t as O}from"./labeled-separator-CV7nb0XW.js";var k=t(e()),A=r(),j={SECOND_FACTOR_SESSION_EXPIRED:`SECOND_FACTOR_SESSION_EXPIRED`,INVALID_TOTP_CODE:`INVALID_TOTP_CODE`,TOTP_NOT_ENABLED:`TOTP_NOT_ENABLED`},M=5;function N(){let{t:e}=n(),t=o(),r=c(),N=g.useSearch(),P=(0,k.useRef)(null),[F,I]=(0,k.useState)(!1),[L,R]=(0,k.useState)(M),z=(0,k.useMemo)(()=>d({code:u().length(6,e(`validation.totp.length`)).regex(/^\d{6}$/,e(`validation.totp.digits`))}),[e]),B=l({...E,onSuccess:async e=>{r.setQueryData(h.queryKey,{user:e.user}),await x(),f(N)?window.location.href=_(N):t.navigate({to:`/profile`})},onSettled:()=>{r.invalidateQueries({queryKey:h.queryKey})}}),{setValue:V,setError:H,handleSubmit:U,watch:W,formState:{errors:G}}=w({defaultValues:{code:``},resolver:T(z)}),K=W(`code`),q=(0,k.useCallback)(()=>{t.navigate({to:`/login`,search:p(N)})},[t,N]);(0,k.useEffect)(()=>{if(!F)return;let e=setInterval(()=>{R(t=>t<=1?(clearInterval(e),q(),0):t-1)},1e3);return()=>clearInterval(e)},[F,q]);let J=async t=>{try{await B.mutateAsync(t)}catch(t){if(t instanceof i)switch(t.code){case j.SECOND_FACTOR_SESSION_EXPIRED:I(!0),R(M);return;case j.TOTP_NOT_ENABLED:q();return;case j.INVALID_TOTP_CODE:H(`code`,{type:`manual`,message:e(`verifyTotp.error.invalid`)}),V(`code`,``),P.current?.focus();return}H(`code`,{type:`manual`,message:e(`verifyTotp.error.invalid`)}),V(`code`,``),P.current?.focus()}};return(0,A.jsxs)(b,{children:[(0,A.jsx)(v,{subtitle:e(`verifyTotp.subtitle`),title:e(`verifyTotp.title`)}),F&&(0,A.jsx)(y,{"data-testid":`totp-verify-session-expired`,icon:m,type:`warning`,children:(0,A.jsxs)(`span`,{className:`flex flex-col items-start gap-tinyrack-3xs`,children:[(0,A.jsx)(`span`,{children:e(`verifyTotp.error.expired`)}),(0,A.jsx)(`span`,{className:`text-tinyrack-sm opacity-tinyrack-hover`,children:e(`verifyTotp.redirecting`,{seconds:L})}),(0,A.jsx)(a,{appearance:`ghost`,intent:`neutral`,onClick:q,type:`button`,uiSize:`sm`,children:e(`verifyTotp.redirectNow`)})]})}),(0,A.jsxs)(`form`,{className:`flex flex-col gap-tinyrack-lg`,onSubmit:U(J),children:[(0,A.jsx)(D,{autoFocus:!0,disabled:F,error:G.code,length:6,onChange:e=>V(`code`,e),onComplete:()=>U(J)(),ref:P,value:K}),F?(0,A.jsx)(a,{className:`w-full`,disabled:!0,intent:`primary`,type:`button`,uiSize:`lg`,children:e(`verifyTotp.submit`)}):(0,A.jsx)(a,{className:`w-full`,intent:`primary`,loading:B.isPending,loadingLabel:e(`verifyTotp.submitting`),type:`submit`,uiSize:`lg`,children:e(`verifyTotp.submit`)})]}),(0,A.jsxs)(`div`,{className:`flex flex-col gap-tinyrack-md`,children:[(0,A.jsx)(O,{label:e(`common.or`)}),(0,A.jsx)(a,{appearance:`ghost`,className:`w-full`,"data-testid":`totp-verify-recovery-link`,intent:`neutral`,onClick:()=>t.navigate({to:`/verify/totp/recovery`,search:p(N)}),type:`button`,children:e(`verifyTotp.useRecoveryCode`)})]}),(0,A.jsx)(C,{children:(0,A.jsx)(S,{link:(0,A.jsx)(s,{search:p(N),to:`/login`,children:e(`verifyTotp.backToLogin`)})})})]})}export{N as component};
2
+ //# sourceMappingURL=totp-Cjns-PoB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"totp-BFJEXxk0.js","names":["standardSchemaResolver","useMutation","useQueryClient","Link","useRouter","TRButton","CircleAlertIcon","useCallback","useEffect","useMemo","useRef","useState","useForm","useTranslation","z","AuthFooter","AuthFooterLink","AuthPageHeader","Alert","LabeledSeparator","PinInput","AuthLayout","IssuaryError","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","getSessionQueryOptions","verifyTotpLoginMutationOptions","ERROR_CODES","SECOND_FACTOR_SESSION_EXPIRED","INVALID_TOTP_CODE","TOTP_NOT_ENABLED","const","Route","VerifyTotpFormValues","code","REDIRECT_COUNTDOWN_SECONDS","VerifyTotp","t","router","queryClient","search","useSearch","pinInputRef","PinInputRef","sessionExpired","setSessionExpired","redirectCountdown","setRedirectCountdown","verifySchema","object","string","length","regex","verifyMutation","onSuccess","data","setQueryData","queryKey","user","window","location","href","navigate","to","onSettled","invalidateQueries","setValue","setError","handleSubmit","watch","formState","errors","defaultValues","resolver","codeValue","redirectToLogin","timer","setInterval","prev","clearInterval","onSubmit","values","mutateAsync","error","type","message","current","focus","seconds","value","isPending","component"],"sources":["../../../frontend/src/routes/verify/totp/index.tsx?tsr-split=component"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { CircleAlertIcon } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { LabeledSeparator } from '#frontend/components/ui/labeled-separator.tsx';\nimport {\n PinInput,\n type PinInputRef,\n} from '#frontend/components/ui/pin-input.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { IssuaryError } from '#frontend/libs/error.ts';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport { verifyTotpLoginMutationOptions } from '#frontend/queries/totp.ts';\n\n/** Error codes from backend */\nconst ERROR_CODES = {\n SECOND_FACTOR_SESSION_EXPIRED: 'SECOND_FACTOR_SESSION_EXPIRED',\n INVALID_TOTP_CODE: 'INVALID_TOTP_CODE',\n TOTP_NOT_ENABLED: 'TOTP_NOT_ENABLED',\n} as const;\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/verify/totp/')({\n component: VerifyTotp,\n validateSearch: SearchSchema,\n});\n\ntype VerifyTotpFormValues = {\n code: string;\n};\n\n/** Auto redirect countdown seconds */\nconst REDIRECT_COUNTDOWN_SECONDS = 5;\n\nfunction VerifyTotp() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const pinInputRef = useRef<PinInputRef>(null);\n\n const [sessionExpired, setSessionExpired] = useState(false);\n const [redirectCountdown, setRedirectCountdown] = useState(\n REDIRECT_COUNTDOWN_SECONDS,\n );\n\n const verifySchema = useMemo(\n () =>\n z.object({\n code: z\n .string()\n .length(6, t('validation.totp.length'))\n .regex(/^\\d{6}$/, t('validation.totp.digits')),\n }),\n [t],\n );\n\n const verifyMutation = useMutation({\n ...verifyTotpLoginMutationOptions,\n onSuccess: async (data) => {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: data.user,\n });\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const {\n setValue,\n setError,\n handleSubmit,\n watch,\n formState: { errors },\n } = useForm<VerifyTotpFormValues>({\n defaultValues: {\n code: '',\n },\n resolver: standardSchemaResolver(verifySchema),\n });\n\n const codeValue = watch('code');\n\n // Auto redirect when session expires\n const redirectToLogin = useCallback(() => {\n router.navigate({\n to: '/login',\n search: extractOAuthParams(search),\n });\n }, [router, search]);\n\n useEffect(() => {\n if (!sessionExpired) return;\n\n const timer = setInterval(() => {\n setRedirectCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n redirectToLogin();\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [sessionExpired, redirectToLogin]);\n\n const onSubmit = async (values: VerifyTotpFormValues) => {\n try {\n await verifyMutation.mutateAsync(values);\n } catch (error) {\n if (error instanceof IssuaryError) {\n switch (error.code) {\n case ERROR_CODES.SECOND_FACTOR_SESSION_EXPIRED:\n // Session expired - show alert and start auto redirect\n setSessionExpired(true);\n setRedirectCountdown(REDIRECT_COUNTDOWN_SECONDS);\n return;\n\n case ERROR_CODES.TOTP_NOT_ENABLED:\n // TOTP not enabled - redirect to login\n redirectToLogin();\n return;\n\n case ERROR_CODES.INVALID_TOTP_CODE:\n // Invalid code - show specific error and clear input\n setError('code', {\n type: 'manual',\n message: t('verifyTotp.error.invalid'),\n });\n setValue('code', '');\n pinInputRef.current?.focus();\n return;\n }\n }\n\n // Generic error fallback\n setError('code', {\n type: 'manual',\n message: t('verifyTotp.error.invalid'),\n });\n setValue('code', '');\n pinInputRef.current?.focus();\n }\n };\n\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('verifyTotp.subtitle')}\n title={t('verifyTotp.title')}\n />\n\n {sessionExpired && (\n <Alert\n data-testid=\"totp-verify-session-expired\"\n icon={CircleAlertIcon}\n type=\"warning\"\n >\n <span className=\"flex flex-col items-start gap-tinyrack-3xs\">\n <span>{t('verifyTotp.error.expired')}</span>\n <span className=\"text-tinyrack-sm opacity-tinyrack-hover\">\n {t('verifyTotp.redirecting', { seconds: redirectCountdown })}\n </span>\n <TRButton\n appearance=\"ghost\"\n intent=\"neutral\"\n onClick={redirectToLogin}\n type=\"button\"\n uiSize=\"sm\"\n >\n {t('verifyTotp.redirectNow')}\n </TRButton>\n </span>\n </Alert>\n )}\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <PinInput\n autoFocus\n disabled={sessionExpired}\n error={errors.code}\n length={6}\n onChange={(value) => setValue('code', value)}\n onComplete={() => handleSubmit(onSubmit)()}\n ref={pinInputRef}\n value={codeValue}\n />\n\n {sessionExpired ? (\n <TRButton\n className=\"w-full\"\n disabled\n intent=\"primary\"\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('verifyTotp.submit')}\n </TRButton>\n ) : (\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={verifyMutation.isPending}\n loadingLabel={t('verifyTotp.submitting')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('verifyTotp.submit')}\n </TRButton>\n )}\n </form>\n\n {/*\n The recovery code is the way out when the authenticator is gone, so it\n sits below the rule rather than in the footer with the navigation.\n */}\n <div className=\"flex flex-col gap-tinyrack-md\">\n <LabeledSeparator label={t('common.or')} />\n <TRButton\n appearance=\"ghost\"\n className=\"w-full\"\n data-testid=\"totp-verify-recovery-link\"\n intent=\"neutral\"\n onClick={() =>\n router.navigate({\n to: '/verify/totp/recovery',\n search: extractOAuthParams(search),\n })\n }\n type=\"button\"\n >\n {t('verifyTotp.useRecoveryCode')}\n </TRButton>\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('verifyTotp.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"totp-Cjns-PoB.js","names":["standardSchemaResolver","useMutation","useQueryClient","Link","useRouter","TRButton","CircleAlertIcon","useCallback","useEffect","useMemo","useRef","useState","useForm","useTranslation","z","AuthFooter","AuthFooterLink","AuthPageHeader","Alert","LabeledSeparator","PinInput","AuthLayout","IssuaryError","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","getSessionQueryOptions","verifyTotpLoginMutationOptions","ERROR_CODES","SECOND_FACTOR_SESSION_EXPIRED","INVALID_TOTP_CODE","TOTP_NOT_ENABLED","const","Route","VerifyTotpFormValues","code","REDIRECT_COUNTDOWN_SECONDS","VerifyTotp","t","router","queryClient","search","useSearch","pinInputRef","PinInputRef","sessionExpired","setSessionExpired","redirectCountdown","setRedirectCountdown","verifySchema","object","string","length","regex","verifyMutation","onSuccess","data","setQueryData","queryKey","user","window","location","href","navigate","to","onSettled","invalidateQueries","setValue","setError","handleSubmit","watch","formState","errors","defaultValues","resolver","codeValue","redirectToLogin","timer","setInterval","prev","clearInterval","onSubmit","values","mutateAsync","error","type","message","current","focus","seconds","value","isPending","component"],"sources":["../../../frontend/src/routes/verify/totp/index.tsx?tsr-split=component"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { CircleAlertIcon } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { LabeledSeparator } from '#frontend/components/ui/labeled-separator.tsx';\nimport {\n PinInput,\n type PinInputRef,\n} from '#frontend/components/ui/pin-input.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { IssuaryError } from '#frontend/libs/error.ts';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport { verifyTotpLoginMutationOptions } from '#frontend/queries/totp.ts';\n\n/** Error codes from backend */\nconst ERROR_CODES = {\n SECOND_FACTOR_SESSION_EXPIRED: 'SECOND_FACTOR_SESSION_EXPIRED',\n INVALID_TOTP_CODE: 'INVALID_TOTP_CODE',\n TOTP_NOT_ENABLED: 'TOTP_NOT_ENABLED',\n} as const;\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/verify/totp/')({\n component: VerifyTotp,\n validateSearch: SearchSchema,\n});\n\ntype VerifyTotpFormValues = {\n code: string;\n};\n\n/** Auto redirect countdown seconds */\nconst REDIRECT_COUNTDOWN_SECONDS = 5;\n\nfunction VerifyTotp() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const pinInputRef = useRef<PinInputRef>(null);\n\n const [sessionExpired, setSessionExpired] = useState(false);\n const [redirectCountdown, setRedirectCountdown] = useState(\n REDIRECT_COUNTDOWN_SECONDS,\n );\n\n const verifySchema = useMemo(\n () =>\n z.object({\n code: z\n .string()\n .length(6, t('validation.totp.length'))\n .regex(/^\\d{6}$/, t('validation.totp.digits')),\n }),\n [t],\n );\n\n const verifyMutation = useMutation({\n ...verifyTotpLoginMutationOptions,\n onSuccess: async (data) => {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: data.user,\n });\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const {\n setValue,\n setError,\n handleSubmit,\n watch,\n formState: { errors },\n } = useForm<VerifyTotpFormValues>({\n defaultValues: {\n code: '',\n },\n resolver: standardSchemaResolver(verifySchema),\n });\n\n const codeValue = watch('code');\n\n // Auto redirect when session expires\n const redirectToLogin = useCallback(() => {\n router.navigate({\n to: '/login',\n search: extractOAuthParams(search),\n });\n }, [router, search]);\n\n useEffect(() => {\n if (!sessionExpired) return;\n\n const timer = setInterval(() => {\n setRedirectCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n redirectToLogin();\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [sessionExpired, redirectToLogin]);\n\n const onSubmit = async (values: VerifyTotpFormValues) => {\n try {\n await verifyMutation.mutateAsync(values);\n } catch (error) {\n if (error instanceof IssuaryError) {\n switch (error.code) {\n case ERROR_CODES.SECOND_FACTOR_SESSION_EXPIRED:\n // Session expired - show alert and start auto redirect\n setSessionExpired(true);\n setRedirectCountdown(REDIRECT_COUNTDOWN_SECONDS);\n return;\n\n case ERROR_CODES.TOTP_NOT_ENABLED:\n // TOTP not enabled - redirect to login\n redirectToLogin();\n return;\n\n case ERROR_CODES.INVALID_TOTP_CODE:\n // Invalid code - show specific error and clear input\n setError('code', {\n type: 'manual',\n message: t('verifyTotp.error.invalid'),\n });\n setValue('code', '');\n pinInputRef.current?.focus();\n return;\n }\n }\n\n // Generic error fallback\n setError('code', {\n type: 'manual',\n message: t('verifyTotp.error.invalid'),\n });\n setValue('code', '');\n pinInputRef.current?.focus();\n }\n };\n\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('verifyTotp.subtitle')}\n title={t('verifyTotp.title')}\n />\n\n {sessionExpired && (\n <Alert\n data-testid=\"totp-verify-session-expired\"\n icon={CircleAlertIcon}\n type=\"warning\"\n >\n <span className=\"flex flex-col items-start gap-tinyrack-3xs\">\n <span>{t('verifyTotp.error.expired')}</span>\n <span className=\"text-tinyrack-sm opacity-tinyrack-hover\">\n {t('verifyTotp.redirecting', { seconds: redirectCountdown })}\n </span>\n <TRButton\n appearance=\"ghost\"\n intent=\"neutral\"\n onClick={redirectToLogin}\n type=\"button\"\n uiSize=\"sm\"\n >\n {t('verifyTotp.redirectNow')}\n </TRButton>\n </span>\n </Alert>\n )}\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <PinInput\n autoFocus\n disabled={sessionExpired}\n error={errors.code}\n length={6}\n onChange={(value) => setValue('code', value)}\n onComplete={() => handleSubmit(onSubmit)()}\n ref={pinInputRef}\n value={codeValue}\n />\n\n {sessionExpired ? (\n <TRButton\n className=\"w-full\"\n disabled\n intent=\"primary\"\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('verifyTotp.submit')}\n </TRButton>\n ) : (\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={verifyMutation.isPending}\n loadingLabel={t('verifyTotp.submitting')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('verifyTotp.submit')}\n </TRButton>\n )}\n </form>\n\n {/*\n The recovery code is the way out when the authenticator is gone, so it\n sits below the rule rather than in the footer with the navigation.\n */}\n <div className=\"flex flex-col gap-tinyrack-md\">\n <LabeledSeparator label={t('common.or')} />\n <TRButton\n appearance=\"ghost\"\n className=\"w-full\"\n data-testid=\"totp-verify-recovery-link\"\n intent=\"neutral\"\n onClick={() =>\n router.navigate({\n to: '/verify/totp/recovery',\n search: extractOAuthParams(search),\n })\n }\n type=\"button\"\n >\n {t('verifyTotp.useRecoveryCode')}\n </TRButton>\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('verifyTotp.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{J as e,U as t,Z as n,i as r,n as i,q as a,r as o,s,t as c}from"./createLucideIcon-BPpAIXR_.js";import{On as ee,Sn as te,Sr as l}from"./config-DTQCUlfZ.js";import{a as u,i as d,n as f,r as p,t as m}from"./use-totp-setup-CtTaklH-.js";import{t as h}from"./shield-check-D_p8H0ni.js";import{F as g,N as _,R as ne,S as re,d as ie,j as v}from"./index-CZ0rWM7l.js";import{t as y}from"./auth-page-header-COvqWINf.js";import{n as b,t as x}from"./alert-leqyvHOp.js";import{t as S}from"./auth-layout-DGTylGtX.js";import{t as C}from"./promise-OpBtq8tG.js";import{n as w,t as T}from"./auth-footer-Cz5ivsDc.js";var ae=c(`circle-x`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),E=n(e()),D=a();function oe({steps:e,current:t,progressLabel:n}){return(0,D.jsxs)(`div`,{className:`flex w-full flex-col gap-tinyrack-xs`,children:[(0,D.jsxs)(`div`,{className:`flex items-baseline justify-between gap-tinyrack-sm`,children:[(0,D.jsx)(i,{color:`muted`,variant:`label`,children:n}),(0,D.jsx)(i,{color:`muted`,variant:`caption`,children:e[t]})]}),(0,D.jsx)(`div`,{"aria-label":n,"aria-valuemax":e.length,"aria-valuemin":1,"aria-valuenow":t+1,className:`flex gap-tinyrack-xs`,role:`progressbar`,children:e.map((e,n)=>(0,D.jsx)(`span`,{"aria-hidden":!0,className:`h-tinyrack-xs flex-1 rounded-tinyrack-full transition-colors duration-tinyrack-normal ease-tinyrack-standard ${n<=t?`bg-tinyrack-primary`:`bg-tinyrack-surface-hover`}`},e))})]})}var O={TOTP_ALREADY_ENABLED:`TOTP_ALREADY_ENABLED`,TOTP_NOT_SETUP:`TOTP_NOT_SETUP`,INVALID_TOTP_CODE:`INVALID_TOTP_CODE`,UNAUTHORIZED:`UNAUTHORIZED`,SECOND_FACTOR_SESSION_EXPIRED:`SECOND_FACTOR_SESSION_EXPIRED`},k=5;function A(){let{t:e}=r(),n=ee(),i=l(),a=ie.useSearch(),[c,A]=(0,E.useState)(`generic`),[j,M]=(0,E.useState)(k),N=(0,E.useCallback)(()=>{n.navigate({to:`/login`,search:_(a)})},[n,a]),P=(0,E.useCallback)(()=>{n.navigate({to:`/profile`})},[n]),{step:F,setupData:I,recoveryCodes:L,isSetupPending:R,isVerifyPending:z,isConfirmPending:B,startSetup:V,verify:H,goToQr:U,goToVerify:W,confirmRecoveryCodes:G}=m({autoStart:!0,onSetupError:(0,E.useCallback)(e=>{if(e instanceof o)switch(e.code){case O.TOTP_ALREADY_ENABLED:A(`already_enabled`);break;case O.UNAUTHORIZED:case O.SECOND_FACTOR_SESSION_EXPIRED:A(`session_expired`),M(k);break;default:A(`generic`)}else A(`generic`)},[]),onVerifySuccess:(0,E.useCallback)(async e=>{await C()},[]),onConfirmSuccess:(0,E.useCallback)(async e=>{i.setQueryData(re.queryKey,{user:e.user}),await C(),g(a)?window.location.href=v(a):n.navigate({to:`/profile`})},[i,n,a]),onVerifyError:e=>{if(e instanceof o)switch(e.code){case O.TOTP_ALREADY_ENABLED:P();break;case O.UNAUTHORIZED:case O.SECOND_FACTOR_SESSION_EXPIRED:A(`session_expired`),M(k);break;case O.TOTP_NOT_SETUP:V();break}}});(0,E.useEffect)(()=>{if(c!==`session_expired`||F!==`error`)return;let e=setInterval(()=>{M(t=>t<=1?(clearInterval(e),N(),0):t-1)},1e3);return()=>clearInterval(e)},[c,F,N]);let K=(0,E.useCallback)(async e=>{await H(e)},[H]),q=F===`recovery`&&L.length>0,J=F===`qr`&&I!==null,Y=F!==`loading`&&F!==`error`,X=1;q?X=2:J&&(X=0);let Z=e(`setupTotp.title`),Q=e(`setupTotp.subtitle`);q?Z=e(`setupTotp.recoveryCodes.title`):Y&&!J&&(Z=e(`setupTotp.verifyTitle`),Q=e(`setupTotp.verifySubtitle`));let se=F===`error`||J,$;return $=F===`loading`?(0,D.jsx)(`div`,{className:`flex justify-center py-tinyrack-xl`,"data-testid":`totp-setup-loading`,children:(0,D.jsx)(t,{uiSize:`lg`,variant:`primary`})}):F===`error`&&c===`session_expired`?(0,D.jsxs)(b.Root,{"data-testid":`totp-setup-session-expired`,variant:`warning`,children:[(0,D.jsx)(ne,{"aria-hidden":!0,className:`size-tinyrack-xl`}),(0,D.jsx)(b.Title,{children:e(`setupTotp.error.expired`)}),(0,D.jsx)(b.Description,{children:e(`setupTotp.redirecting`,{seconds:j})}),(0,D.jsx)(b.Actions,{children:(0,D.jsx)(s,{appearance:`ghost`,intent:`neutral`,onClick:N,type:`button`,uiSize:`sm`,children:e(`setupTotp.redirectNow`)})})]}):F===`error`&&c===`already_enabled`?(0,D.jsxs)(D.Fragment,{children:[(0,D.jsx)(x,{icon:u,type:`info`,children:e(`setupTotp.error.alreadyEnabled`)}),(0,D.jsx)(s,{className:`w-full`,intent:`primary`,onClick:P,type:`button`,uiSize:`lg`,children:e(`setupTotp.goToProfile`)})]}):F===`error`?(0,D.jsxs)(D.Fragment,{children:[(0,D.jsx)(x,{icon:ae,type:`error`,children:e(`setupTotp.error.setupFailed`)}),(0,D.jsx)(s,{className:`w-full`,disabled:R,intent:`primary`,loading:R,onClick:V,type:`button`,uiSize:`lg`,children:e(`setupTotp.retry`)})]}):q?(0,D.jsx)(d,{isLoading:B,onConfirm:G,recoveryCodes:L}):J&&I?(0,D.jsxs)(D.Fragment,{children:[(0,D.jsx)(x,{icon:h,type:`info`,children:e(`setupTotp.required`)}),(0,D.jsx)(f,{onNext:W,setupData:I})]}):(0,D.jsx)(p,{isPending:z,onBack:U,onSubmit:K}),(0,D.jsxs)(S,{width:q?`wide`:`form`,children:[(0,D.jsx)(y,{eyebrow:Y?(0,D.jsx)(oe,{current:X,progressLabel:e(`setupTotp.stepProgress`,{current:X+1,total:3}),steps:[e(`setupTotp.steps.scan`),e(`setupTotp.steps.verify`),e(`setupTotp.steps.saveCodes`)]}):void 0,subtitle:Q,title:Z}),(0,D.jsx)(`div`,{className:`auth-enter flex flex-col gap-tinyrack-lg`,children:$},F),se&&(0,D.jsx)(T,{children:(0,D.jsx)(w,{link:(0,D.jsx)(te,{search:_(a),to:`/login`,children:e(`setupTotp.backToLogin`)})})})]})}export{A as component};
2
+ //# sourceMappingURL=totp-g0-s50HZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"totp-MINhsVxT.js","names":["useQueryClient","Link","useRouter","TRAlert","TRButton","TRSpinner","CircleAlertIcon","CircleXIcon","InfoIcon","ShieldCheckIcon","useCallback","useEffect","useState","useTranslation","AuthFooter","AuthFooterLink","AuthPageHeader","AuthSteps","QrStep","RecoveryCodesStep","VerifyStep","Alert","AuthLayout","useTotpSetup","IssuaryError","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","getSessionQueryOptions","ERROR_CODES","TOTP_ALREADY_ENABLED","TOTP_NOT_SETUP","INVALID_TOTP_CODE","UNAUTHORIZED","SECOND_FACTOR_SESSION_EXPIRED","const","Route","ErrorType","REDIRECT_COUNTDOWN_SECONDS","SetupTotp","t","router","queryClient","search","useSearch","errorType","setErrorType","redirectCountdown","setRedirectCountdown","redirectToLogin","navigate","to","redirectToProfile","handleSetupError","error","Error","code","handleVerifySuccess","_data","TotpSetupVerifyResponse","handleConfirmSuccess","data","TotpConfirmResponse","setQueryData","queryKey","user","window","location","href","step","setupData","recoveryCodes","isSetupPending","isVerifyPending","isConfirmPending","startSetup","verify","goToQr","goToVerify","confirmRecoveryCodes","autoStart","onSetupError","onVerifySuccess","onConfirmSuccess","onVerifyError","timer","setInterval","prev","clearInterval","handleVerify","isRecoveryStep","length","isQrStep","isWizardStep","wizardIndex","title","subtitle","showFooter","body","React","ReactNode","seconds","current","total","undefined","component"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.25.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-x.mjs","../../../frontend/src/components/auth/auth-steps.tsx","../../../frontend/src/routes/setup/totp/index.tsx?tsr-split=component"],"sourcesContent":["/**\n * @license lucide-react v1.25.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"10\", key: \"1mglay\" }],\n [\"path\", { d: \"m15 9-6 6\", key: \"1uzhvr\" }],\n [\"path\", { d: \"m9 9 6 6\", key: \"z0biqf\" }]\n];\nconst CircleX = createLucideIcon(\"circle-x\", __iconNode);\n\nexport { __iconNode, CircleX as default };\n//# sourceMappingURL=circle-x.mjs.map\n","import { TRText } from '@tinyrack/ui/components/text';\n\ntype AuthStepsProps = {\n /** Ordered step labels. The current one is announced to assistive tech. */\n steps: string[];\n /** Zero-based index of the step being shown. */\n current: number;\n /** e.g. \"Step {{current}} of {{total}}\" — already interpolated by the caller. */\n progressLabel: string;\n};\n\n/**\n * Progress indicator for the multi-step auth wizards.\n *\n * Not built on `TRSteps`: that component is a vertical, docs-prose ordered\n * list with an inline-start rail and absolutely positioned counters, and it\n * has no notion of a current or completed step. Forcing it horizontal would\n * mean overriding most of its CSS. A stateful, orientable stepper is worth\n * proposing upstream; until then this is a small token-only composition.\n *\n * Renders as a labelled group rather than a list, because the segments are\n * decorative — the text label carries the information.\n */\nexport function AuthSteps({ steps, current, progressLabel }: AuthStepsProps) {\n return (\n <div className=\"flex w-full flex-col gap-tinyrack-xs\">\n <div className=\"flex items-baseline justify-between gap-tinyrack-sm\">\n <TRText color=\"muted\" variant=\"label\">\n {progressLabel}\n </TRText>\n <TRText color=\"muted\" variant=\"caption\">\n {steps[current]}\n </TRText>\n </div>\n <div\n aria-label={progressLabel}\n aria-valuemax={steps.length}\n aria-valuemin={1}\n aria-valuenow={current + 1}\n className=\"flex gap-tinyrack-xs\"\n role=\"progressbar\"\n >\n {steps.map((step, index) => (\n <span\n aria-hidden\n className={`h-tinyrack-xs flex-1 rounded-tinyrack-full transition-colors duration-tinyrack-normal ease-tinyrack-standard ${\n index <= current\n ? 'bg-tinyrack-primary'\n : 'bg-tinyrack-surface-hover'\n }`}\n key={step}\n />\n ))}\n </div>\n </div>\n );\n}\n","import { useQueryClient } from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRAlert } from '@tinyrack/ui/components/alert';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRSpinner } from '@tinyrack/ui/components/spinner';\nimport {\n CircleAlertIcon,\n CircleXIcon,\n InfoIcon,\n ShieldCheckIcon,\n} from 'lucide-react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthSteps } from '#frontend/components/auth/auth-steps.tsx';\nimport { QrStep } from '#frontend/components/totp/qr-step.tsx';\nimport { RecoveryCodesStep } from '#frontend/components/totp/recovery-codes-step.tsx';\nimport { VerifyStep } from '#frontend/components/totp/verify-step.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { useTotpSetup } from '#frontend/features/totp/use-totp-setup.ts';\nimport { IssuaryError } from '#frontend/libs/error.ts';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport type {\n TotpConfirmResponse,\n TotpSetupVerifyResponse,\n} from '#frontend/queries/totp.ts';\n\n/** Error codes from backend */\nconst ERROR_CODES = {\n TOTP_ALREADY_ENABLED: 'TOTP_ALREADY_ENABLED',\n TOTP_NOT_SETUP: 'TOTP_NOT_SETUP',\n INVALID_TOTP_CODE: 'INVALID_TOTP_CODE',\n UNAUTHORIZED: 'UNAUTHORIZED',\n SECOND_FACTOR_SESSION_EXPIRED: 'SECOND_FACTOR_SESSION_EXPIRED',\n} as const;\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/setup/totp/')({\n component: SetupTotp,\n validateSearch: SearchSchema,\n});\n\ntype ErrorType = 'generic' | 'already_enabled' | 'session_expired';\n\n/** Auto redirect countdown seconds */\nconst REDIRECT_COUNTDOWN_SECONDS = 5;\n\nfunction SetupTotp() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const [errorType, setErrorType] = useState<ErrorType>('generic');\n const [redirectCountdown, setRedirectCountdown] = useState(\n REDIRECT_COUNTDOWN_SECONDS,\n );\n\n const redirectToLogin = useCallback(() => {\n router.navigate({\n to: '/login',\n search: extractOAuthParams(search),\n });\n }, [router, search]);\n\n const redirectToProfile = useCallback(() => {\n router.navigate({ to: '/profile' });\n }, [router]);\n\n const handleSetupError = useCallback((error: Error) => {\n if (error instanceof IssuaryError) {\n switch (error.code) {\n case ERROR_CODES.TOTP_ALREADY_ENABLED:\n setErrorType('already_enabled');\n break;\n case ERROR_CODES.UNAUTHORIZED:\n case ERROR_CODES.SECOND_FACTOR_SESSION_EXPIRED:\n setErrorType('session_expired');\n setRedirectCountdown(REDIRECT_COUNTDOWN_SECONDS);\n break;\n default:\n setErrorType('generic');\n }\n } else {\n setErrorType('generic');\n }\n }, []);\n\n const handleVerifySuccess = useCallback(\n async (_data: TotpSetupVerifyResponse) => {\n // Recovery codes step is handled by useTotpSetup hook\n // (step transitions to 'recovery' automatically)\n // Session update happens after confirm, not here\n await tick();\n },\n [],\n );\n\n const handleConfirmSuccess = useCallback(\n async (data: TotpConfirmResponse) => {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: data.user,\n });\n await tick();\n // Navigate after successful confirm\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n [queryClient, router, search],\n );\n\n const {\n step,\n setupData,\n recoveryCodes,\n isSetupPending,\n isVerifyPending,\n isConfirmPending,\n startSetup,\n verify,\n goToQr,\n goToVerify,\n confirmRecoveryCodes,\n } = useTotpSetup({\n autoStart: true,\n onSetupError: handleSetupError,\n onVerifySuccess: handleVerifySuccess,\n onConfirmSuccess: handleConfirmSuccess,\n onVerifyError: (error) => {\n if (error instanceof IssuaryError) {\n switch (error.code) {\n case ERROR_CODES.TOTP_ALREADY_ENABLED:\n redirectToProfile();\n break;\n case ERROR_CODES.UNAUTHORIZED:\n case ERROR_CODES.SECOND_FACTOR_SESSION_EXPIRED:\n setErrorType('session_expired');\n setRedirectCountdown(REDIRECT_COUNTDOWN_SECONDS);\n break;\n case ERROR_CODES.TOTP_NOT_SETUP:\n startSetup();\n break;\n }\n }\n },\n });\n\n // Auto redirect when session expires\n useEffect(() => {\n if (errorType !== 'session_expired' || step !== 'error') return;\n\n const timer = setInterval(() => {\n setRedirectCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n redirectToLogin();\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [errorType, step, redirectToLogin]);\n\n const handleVerify = useCallback(\n async (code: string) => {\n await verify(code);\n },\n [verify],\n );\n\n const isRecoveryStep = step === 'recovery' && recoveryCodes.length > 0;\n const isQrStep = step === 'qr' && setupData !== null;\n // `loading` and `error` are not points on the journey, so the wizard's\n // progress is hidden there rather than showing a misleading step.\n const isWizardStep = step !== 'loading' && step !== 'error';\n\n let wizardIndex = 1;\n if (isRecoveryStep) {\n wizardIndex = 2;\n } else if (isQrStep) {\n wizardIndex = 0;\n }\n\n let title = t('setupTotp.title');\n let subtitle: string | undefined = t('setupTotp.subtitle');\n if (isRecoveryStep) {\n title = t('setupTotp.recoveryCodes.title');\n } else if (isWizardStep && !isQrStep) {\n title = t('setupTotp.verifyTitle');\n subtitle = t('setupTotp.verifySubtitle');\n }\n\n // Where the original screens offered a way out: the wizard's later steps\n // deliberately do not, so nobody leaves before saving their codes.\n const showFooter = step === 'error' || isQrStep;\n\n let body: React.ReactNode;\n if (step === 'loading') {\n body = (\n <div\n className=\"flex justify-center py-tinyrack-xl\"\n data-testid=\"totp-setup-loading\"\n >\n <TRSpinner uiSize=\"lg\" variant=\"primary\" />\n </div>\n );\n } else if (step === 'error' && errorType === 'session_expired') {\n body = (\n <TRAlert.Root data-testid=\"totp-setup-session-expired\" variant=\"warning\">\n <CircleAlertIcon aria-hidden className=\"size-tinyrack-xl\" />\n <TRAlert.Title>{t('setupTotp.error.expired')}</TRAlert.Title>\n <TRAlert.Description>\n {t('setupTotp.redirecting', {\n seconds: redirectCountdown,\n })}\n </TRAlert.Description>\n <TRAlert.Actions>\n <TRButton\n appearance=\"ghost\"\n intent=\"neutral\"\n onClick={redirectToLogin}\n type=\"button\"\n uiSize=\"sm\"\n >\n {t('setupTotp.redirectNow')}\n </TRButton>\n </TRAlert.Actions>\n </TRAlert.Root>\n );\n } else if (step === 'error' && errorType === 'already_enabled') {\n body = (\n <>\n <Alert icon={InfoIcon} type=\"info\">\n {t('setupTotp.error.alreadyEnabled')}\n </Alert>\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n onClick={redirectToProfile}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupTotp.goToProfile')}\n </TRButton>\n </>\n );\n } else if (step === 'error') {\n body = (\n <>\n <Alert icon={CircleXIcon} type=\"error\">\n {t('setupTotp.error.setupFailed')}\n </Alert>\n <TRButton\n className=\"w-full\"\n disabled={isSetupPending}\n intent=\"primary\"\n loading={isSetupPending}\n onClick={startSetup}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupTotp.retry')}\n </TRButton>\n </>\n );\n } else if (isRecoveryStep) {\n body = (\n <RecoveryCodesStep\n isLoading={isConfirmPending}\n onConfirm={confirmRecoveryCodes}\n recoveryCodes={recoveryCodes}\n />\n );\n } else if (isQrStep && setupData) {\n body = (\n <>\n <Alert icon={ShieldCheckIcon} type=\"info\">\n {t('setupTotp.required')}\n </Alert>\n <QrStep onNext={goToVerify} setupData={setupData} />\n </>\n );\n } else {\n body = (\n <VerifyStep\n isPending={isVerifyPending}\n onBack={goToQr}\n onSubmit={handleVerify}\n />\n );\n }\n\n /*\n One shell, one header. The seven `AuthLayout` blocks this replaced meant\n the header remounted on every transition, so the title flashed and the\n progress had nowhere to live.\n */\n return (\n <AuthLayout width={isRecoveryStep ? 'wide' : 'form'}>\n <AuthPageHeader\n eyebrow={\n isWizardStep ? (\n <AuthSteps\n current={wizardIndex}\n progressLabel={t('setupTotp.stepProgress', {\n current: wizardIndex + 1,\n total: 3,\n })}\n steps={[\n t('setupTotp.steps.scan'),\n t('setupTotp.steps.verify'),\n t('setupTotp.steps.saveCodes'),\n ]}\n />\n ) : undefined\n }\n subtitle={subtitle}\n title={title}\n />\n\n {/* Keyed so each step animates in rather than swapping in place. */}\n <div className=\"auth-enter flex flex-col gap-tinyrack-lg\" key={step}>\n {body}\n </div>\n\n {showFooter && (\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupTotp.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n )}\n </AuthLayout>\n );\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
1
+ {"version":3,"file":"totp-g0-s50HZ.js","names":["useQueryClient","Link","useRouter","TRAlert","TRButton","TRSpinner","CircleAlertIcon","CircleXIcon","InfoIcon","ShieldCheckIcon","useCallback","useEffect","useState","useTranslation","AuthFooter","AuthFooterLink","AuthPageHeader","AuthSteps","QrStep","RecoveryCodesStep","VerifyStep","Alert","AuthLayout","useTotpSetup","IssuaryError","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","getSessionQueryOptions","ERROR_CODES","TOTP_ALREADY_ENABLED","TOTP_NOT_SETUP","INVALID_TOTP_CODE","UNAUTHORIZED","SECOND_FACTOR_SESSION_EXPIRED","const","Route","ErrorType","REDIRECT_COUNTDOWN_SECONDS","SetupTotp","t","router","queryClient","search","useSearch","errorType","setErrorType","redirectCountdown","setRedirectCountdown","redirectToLogin","navigate","to","redirectToProfile","handleSetupError","error","Error","code","handleVerifySuccess","_data","TotpSetupVerifyResponse","handleConfirmSuccess","data","TotpConfirmResponse","setQueryData","queryKey","user","window","location","href","step","setupData","recoveryCodes","isSetupPending","isVerifyPending","isConfirmPending","startSetup","verify","goToQr","goToVerify","confirmRecoveryCodes","autoStart","onSetupError","onVerifySuccess","onConfirmSuccess","onVerifyError","timer","setInterval","prev","clearInterval","handleVerify","isRecoveryStep","length","isQrStep","isWizardStep","wizardIndex","title","subtitle","showFooter","body","React","ReactNode","seconds","current","total","undefined","component"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.25.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-x.mjs","../../../frontend/src/components/auth/auth-steps.tsx","../../../frontend/src/routes/setup/totp/index.tsx?tsr-split=component"],"sourcesContent":["/**\n * @license lucide-react v1.25.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"circle\", { cx: \"12\", cy: \"12\", r: \"10\", key: \"1mglay\" }],\n [\"path\", { d: \"m15 9-6 6\", key: \"1uzhvr\" }],\n [\"path\", { d: \"m9 9 6 6\", key: \"z0biqf\" }]\n];\nconst CircleX = createLucideIcon(\"circle-x\", __iconNode);\n\nexport { __iconNode, CircleX as default };\n//# sourceMappingURL=circle-x.mjs.map\n","import { TRText } from '@tinyrack/ui/components/text';\n\ntype AuthStepsProps = {\n /** Ordered step labels. The current one is announced to assistive tech. */\n steps: string[];\n /** Zero-based index of the step being shown. */\n current: number;\n /** e.g. \"Step {{current}} of {{total}}\" — already interpolated by the caller. */\n progressLabel: string;\n};\n\n/**\n * Progress indicator for the multi-step auth wizards.\n *\n * Not built on `TRSteps`: that component is a vertical, docs-prose ordered\n * list with an inline-start rail and absolutely positioned counters, and it\n * has no notion of a current or completed step. Forcing it horizontal would\n * mean overriding most of its CSS. A stateful, orientable stepper is worth\n * proposing upstream; until then this is a small token-only composition.\n *\n * Renders as a labelled group rather than a list, because the segments are\n * decorative — the text label carries the information.\n */\nexport function AuthSteps({ steps, current, progressLabel }: AuthStepsProps) {\n return (\n <div className=\"flex w-full flex-col gap-tinyrack-xs\">\n <div className=\"flex items-baseline justify-between gap-tinyrack-sm\">\n <TRText color=\"muted\" variant=\"label\">\n {progressLabel}\n </TRText>\n <TRText color=\"muted\" variant=\"caption\">\n {steps[current]}\n </TRText>\n </div>\n <div\n aria-label={progressLabel}\n aria-valuemax={steps.length}\n aria-valuemin={1}\n aria-valuenow={current + 1}\n className=\"flex gap-tinyrack-xs\"\n role=\"progressbar\"\n >\n {steps.map((step, index) => (\n <span\n aria-hidden\n className={`h-tinyrack-xs flex-1 rounded-tinyrack-full transition-colors duration-tinyrack-normal ease-tinyrack-standard ${\n index <= current\n ? 'bg-tinyrack-primary'\n : 'bg-tinyrack-surface-hover'\n }`}\n key={step}\n />\n ))}\n </div>\n </div>\n );\n}\n","import { useQueryClient } from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRAlert } from '@tinyrack/ui/components/alert';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRSpinner } from '@tinyrack/ui/components/spinner';\nimport {\n CircleAlertIcon,\n CircleXIcon,\n InfoIcon,\n ShieldCheckIcon,\n} from 'lucide-react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthSteps } from '#frontend/components/auth/auth-steps.tsx';\nimport { QrStep } from '#frontend/components/totp/qr-step.tsx';\nimport { RecoveryCodesStep } from '#frontend/components/totp/recovery-codes-step.tsx';\nimport { VerifyStep } from '#frontend/components/totp/verify-step.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport { useTotpSetup } from '#frontend/features/totp/use-totp-setup.ts';\nimport { IssuaryError } from '#frontend/libs/error.ts';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\nimport type {\n TotpConfirmResponse,\n TotpSetupVerifyResponse,\n} from '#frontend/queries/totp.ts';\n\n/** Error codes from backend */\nconst ERROR_CODES = {\n TOTP_ALREADY_ENABLED: 'TOTP_ALREADY_ENABLED',\n TOTP_NOT_SETUP: 'TOTP_NOT_SETUP',\n INVALID_TOTP_CODE: 'INVALID_TOTP_CODE',\n UNAUTHORIZED: 'UNAUTHORIZED',\n SECOND_FACTOR_SESSION_EXPIRED: 'SECOND_FACTOR_SESSION_EXPIRED',\n} as const;\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/setup/totp/')({\n component: SetupTotp,\n validateSearch: SearchSchema,\n});\n\ntype ErrorType = 'generic' | 'already_enabled' | 'session_expired';\n\n/** Auto redirect countdown seconds */\nconst REDIRECT_COUNTDOWN_SECONDS = 5;\n\nfunction SetupTotp() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const [errorType, setErrorType] = useState<ErrorType>('generic');\n const [redirectCountdown, setRedirectCountdown] = useState(\n REDIRECT_COUNTDOWN_SECONDS,\n );\n\n const redirectToLogin = useCallback(() => {\n router.navigate({\n to: '/login',\n search: extractOAuthParams(search),\n });\n }, [router, search]);\n\n const redirectToProfile = useCallback(() => {\n router.navigate({ to: '/profile' });\n }, [router]);\n\n const handleSetupError = useCallback((error: Error) => {\n if (error instanceof IssuaryError) {\n switch (error.code) {\n case ERROR_CODES.TOTP_ALREADY_ENABLED:\n setErrorType('already_enabled');\n break;\n case ERROR_CODES.UNAUTHORIZED:\n case ERROR_CODES.SECOND_FACTOR_SESSION_EXPIRED:\n setErrorType('session_expired');\n setRedirectCountdown(REDIRECT_COUNTDOWN_SECONDS);\n break;\n default:\n setErrorType('generic');\n }\n } else {\n setErrorType('generic');\n }\n }, []);\n\n const handleVerifySuccess = useCallback(\n async (_data: TotpSetupVerifyResponse) => {\n // Recovery codes step is handled by useTotpSetup hook\n // (step transitions to 'recovery' automatically)\n // Session update happens after confirm, not here\n await tick();\n },\n [],\n );\n\n const handleConfirmSuccess = useCallback(\n async (data: TotpConfirmResponse) => {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: data.user,\n });\n await tick();\n // Navigate after successful confirm\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n },\n [queryClient, router, search],\n );\n\n const {\n step,\n setupData,\n recoveryCodes,\n isSetupPending,\n isVerifyPending,\n isConfirmPending,\n startSetup,\n verify,\n goToQr,\n goToVerify,\n confirmRecoveryCodes,\n } = useTotpSetup({\n autoStart: true,\n onSetupError: handleSetupError,\n onVerifySuccess: handleVerifySuccess,\n onConfirmSuccess: handleConfirmSuccess,\n onVerifyError: (error) => {\n if (error instanceof IssuaryError) {\n switch (error.code) {\n case ERROR_CODES.TOTP_ALREADY_ENABLED:\n redirectToProfile();\n break;\n case ERROR_CODES.UNAUTHORIZED:\n case ERROR_CODES.SECOND_FACTOR_SESSION_EXPIRED:\n setErrorType('session_expired');\n setRedirectCountdown(REDIRECT_COUNTDOWN_SECONDS);\n break;\n case ERROR_CODES.TOTP_NOT_SETUP:\n startSetup();\n break;\n }\n }\n },\n });\n\n // Auto redirect when session expires\n useEffect(() => {\n if (errorType !== 'session_expired' || step !== 'error') return;\n\n const timer = setInterval(() => {\n setRedirectCountdown((prev) => {\n if (prev <= 1) {\n clearInterval(timer);\n redirectToLogin();\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n\n return () => clearInterval(timer);\n }, [errorType, step, redirectToLogin]);\n\n const handleVerify = useCallback(\n async (code: string) => {\n await verify(code);\n },\n [verify],\n );\n\n const isRecoveryStep = step === 'recovery' && recoveryCodes.length > 0;\n const isQrStep = step === 'qr' && setupData !== null;\n // `loading` and `error` are not points on the journey, so the wizard's\n // progress is hidden there rather than showing a misleading step.\n const isWizardStep = step !== 'loading' && step !== 'error';\n\n let wizardIndex = 1;\n if (isRecoveryStep) {\n wizardIndex = 2;\n } else if (isQrStep) {\n wizardIndex = 0;\n }\n\n let title = t('setupTotp.title');\n let subtitle: string | undefined = t('setupTotp.subtitle');\n if (isRecoveryStep) {\n title = t('setupTotp.recoveryCodes.title');\n } else if (isWizardStep && !isQrStep) {\n title = t('setupTotp.verifyTitle');\n subtitle = t('setupTotp.verifySubtitle');\n }\n\n // Where the original screens offered a way out: the wizard's later steps\n // deliberately do not, so nobody leaves before saving their codes.\n const showFooter = step === 'error' || isQrStep;\n\n let body: React.ReactNode;\n if (step === 'loading') {\n body = (\n <div\n className=\"flex justify-center py-tinyrack-xl\"\n data-testid=\"totp-setup-loading\"\n >\n <TRSpinner uiSize=\"lg\" variant=\"primary\" />\n </div>\n );\n } else if (step === 'error' && errorType === 'session_expired') {\n body = (\n <TRAlert.Root data-testid=\"totp-setup-session-expired\" variant=\"warning\">\n <CircleAlertIcon aria-hidden className=\"size-tinyrack-xl\" />\n <TRAlert.Title>{t('setupTotp.error.expired')}</TRAlert.Title>\n <TRAlert.Description>\n {t('setupTotp.redirecting', {\n seconds: redirectCountdown,\n })}\n </TRAlert.Description>\n <TRAlert.Actions>\n <TRButton\n appearance=\"ghost\"\n intent=\"neutral\"\n onClick={redirectToLogin}\n type=\"button\"\n uiSize=\"sm\"\n >\n {t('setupTotp.redirectNow')}\n </TRButton>\n </TRAlert.Actions>\n </TRAlert.Root>\n );\n } else if (step === 'error' && errorType === 'already_enabled') {\n body = (\n <>\n <Alert icon={InfoIcon} type=\"info\">\n {t('setupTotp.error.alreadyEnabled')}\n </Alert>\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n onClick={redirectToProfile}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupTotp.goToProfile')}\n </TRButton>\n </>\n );\n } else if (step === 'error') {\n body = (\n <>\n <Alert icon={CircleXIcon} type=\"error\">\n {t('setupTotp.error.setupFailed')}\n </Alert>\n <TRButton\n className=\"w-full\"\n disabled={isSetupPending}\n intent=\"primary\"\n loading={isSetupPending}\n onClick={startSetup}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupTotp.retry')}\n </TRButton>\n </>\n );\n } else if (isRecoveryStep) {\n body = (\n <RecoveryCodesStep\n isLoading={isConfirmPending}\n onConfirm={confirmRecoveryCodes}\n recoveryCodes={recoveryCodes}\n />\n );\n } else if (isQrStep && setupData) {\n body = (\n <>\n <Alert icon={ShieldCheckIcon} type=\"info\">\n {t('setupTotp.required')}\n </Alert>\n <QrStep onNext={goToVerify} setupData={setupData} />\n </>\n );\n } else {\n body = (\n <VerifyStep\n isPending={isVerifyPending}\n onBack={goToQr}\n onSubmit={handleVerify}\n />\n );\n }\n\n /*\n One shell, one header. The seven `AuthLayout` blocks this replaced meant\n the header remounted on every transition, so the title flashed and the\n progress had nowhere to live.\n */\n return (\n <AuthLayout width={isRecoveryStep ? 'wide' : 'form'}>\n <AuthPageHeader\n eyebrow={\n isWizardStep ? (\n <AuthSteps\n current={wizardIndex}\n progressLabel={t('setupTotp.stepProgress', {\n current: wizardIndex + 1,\n total: 3,\n })}\n steps={[\n t('setupTotp.steps.scan'),\n t('setupTotp.steps.verify'),\n t('setupTotp.steps.saveCodes'),\n ]}\n />\n ) : undefined\n }\n subtitle={subtitle}\n title={title}\n />\n\n {/* Keyed so each step animates in rather than swapping in place. */}\n <div className=\"auth-enter flex flex-col gap-tinyrack-lg\" key={step}>\n {body}\n </div>\n\n {showFooter && (\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupTotp.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n )}\n </AuthLayout>\n );\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import{J as e,M as t,Z as n,b as r,c as i,h as a,i as o,l as s,m as c,q as l,s as u,t as d,v as f,x as p}from"./createLucideIcon-BPpAIXR_.js";import{P as m,Sr as h,Y as g,_t as _,bn as v,dn as y,et as b,f as x,gt as S,k as C,mt as w,pn as T,pt as E}from"./config-DTQCUlfZ.js";import{t as D}from"./useMutation-BWZ0G4Td.js";import{O,T as k,X as A}from"./shell-header-bar-CgyNXbPS.js";import{t as j}from"./triangle-alert-CqNn0kPs.js";import{c as M,o as N}from"./zod-C87S6Tec.js";import{S as P}from"./index-CZ0rWM7l.js";import{t as F}from"./alert-leqyvHOp.js";import{t as I}from"./checkbox-CFVDS3zJ.js";import{t as L}from"./field-89zKYDVL.js";import{r as R,t as z}from"./standard-schema-e8gtSzGE.js";import{i as B,s as V,t as H}from"./totp-BNZ904Zm.js";import{t as U}from"./pin-input-BGOLfkwL.js";var ee=d(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),te=d(`info`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 16v-4`,key:`1dtifu`}],[`path`,{d:`M12 8h.01`,key:`e9boi3`}]]),W=n(e(),1),G=l();function ne({recoveryCodes:e,onConfirm:t,isLoading:n=!1,className:r=``}){let{t:i}=o(),a=x.useToastManager(),[s,c]=(0,W.useState)(!1),l=(0,W.useCallback)(async()=>{await navigator.clipboard.writeText(e.join(`
2
+ `)),a.add({title:i(`setupTotp.recoveryCodes.copied`),type:`success`})},[e,a,i]);return(0,G.jsxs)(`div`,{className:`flex flex-col gap-tinyrack-lg ${r}`,children:[(0,G.jsx)(`p`,{className:`text-tinyrack-sm text-tinyrack-text-muted`,children:i(`setupTotp.recoveryCodes.description`)}),(0,G.jsx)(`div`,{className:`grid grid-cols-2 gap-tinyrack-sm rounded-tinyrack-lg border border-tinyrack-border bg-tinyrack-surface p-tinyrack-lg`,"data-testid":`recovery-codes-grid`,children:e.map(e=>(0,G.jsx)(`code`,{className:`rounded-tinyrack-sm bg-tinyrack-surface-muted px-tinyrack-sm py-tinyrack-3xs text-center font-tinyrack-mono text-tinyrack-sm text-tinyrack-text`,children:e},e))}),(0,G.jsx)(F,{icon:j,type:`warning`,children:i(`setupTotp.recoveryCodes.warning`)}),(0,G.jsxs)(u,{appearance:`outline`,className:`w-full gap-tinyrack-sm`,intent:`neutral`,onClick:l,type:`button`,children:[(0,G.jsx)(ee,{"aria-hidden":!0,className:`size-tinyrack-lg`}),i(`setupTotp.recoveryCodes.copy`)]}),(0,G.jsx)(L.Root,{children:(0,G.jsxs)(`div`,{className:`flex items-center gap-tinyrack-sm`,children:[(0,G.jsx)(I.Root,{checked:s,"data-testid":`recovery-codes-confirm`,disabled:n,onCheckedChange:e=>c(e===!0),uiSize:`md`,children:(0,G.jsx)(I.Indicator,{})}),(0,G.jsx)(L.Label,{className:`cursor-pointer`,children:i(`setupTotp.recoveryCodes.confirmCheckbox`)})]})}),(0,G.jsx)(u,{className:`w-full`,"data-testid":`recovery-codes-submit`,disabled:!s||n,intent:`primary`,loading:n,onClick:t,type:`button`,uiSize:`lg`,children:i(`setupTotp.recoveryCodes.confirm`)})]})}function re({onSubmit:e,onBack:t,isPending:n,invalidMessage:r,submitLabel:i,pendingText:a,backLabel:s,className:c=``}){let{t:l}=o(),d=(0,W.useRef)(null),{setValue:f,setError:p,handleSubmit:m,watch:h,formState:{errors:g}}=R({defaultValues:{code:``},resolver:z((0,W.useMemo)(()=>N({code:M().length(6,l(`validation.totp.length`)).regex(/^\d{6}$/,l(`validation.totp.digits`))}),[l]))}),_=h(`code`),v=(0,W.useCallback)(async t=>{try{await e(t.code)}catch{p(`code`,{type:`manual`,message:r??l(`setupTotp.error.invalid`)}),f(`code`,``),d.current?.focus()}},[r,e,p,f,l]);return(0,G.jsxs)(`div`,{className:`flex flex-col gap-tinyrack-lg ${c}`,children:[(0,G.jsxs)(`form`,{className:`flex flex-col gap-tinyrack-lg`,onSubmit:m(v),children:[(0,G.jsx)(U,{autoFocus:!0,error:g.code,length:6,onChange:e=>f(`code`,e),onComplete:()=>m(v)(),ref:d,value:_}),(0,G.jsx)(u,{className:`w-full`,intent:`primary`,loading:n,loadingLabel:a??l(`setupTotp.verifying`),type:`submit`,uiSize:`lg`,children:i??l(`setupTotp.verify`)})]}),t&&(0,G.jsx)(u,{appearance:`ghost`,className:`w-full`,"data-testid":`totp-verify-back`,disabled:n,intent:`neutral`,onClick:t,type:`button`,children:s??l(`setupTotp.back`)})]})}function ie(e){let{open:t,defaultOpen:n,onOpenChange:r,disabled:i}=e,[a,o]=k({controlled:t,default:n,name:`Collapsible`,state:`open`}),{mounted:s,setMounted:c,transitionStatus:l}=A(a,!0,!0),u=O(),[d,f]=W.useState(),h=d===null?void 0:d??u,g=p(e=>{let t=!a,n=m(b,e.nativeEvent);r(t,n),!n.isCanceled&&o(t)});return W.useMemo(()=>({defaultPanelId:u,disabled:i,handleTrigger:g,mounted:s,open:a,panelId:h,setMounted:c,setOpen:o,setPanelIdState:f,transitionStatus:l}),[u,i,g,s,a,h,c,o,f,l])}var K=W.createContext(void 0);function q(){let e=W.useContext(K);if(e===void 0)throw Error(f(15));return e}var J=function(e){return e.open=`data-open`,e.closed=`data-closed`,e[e.startingStyle=S.startingStyle]=`startingStyle`,e[e.endingStyle=S.endingStyle]=`endingStyle`,e}({}),ae=function(e){return e.panelOpen=`data-panel-open`,e}({}),oe={[J.open]:``},se={[J.closed]:``},ce={open(e){return e?{[ae.panelOpen]:``}:null}},Y={open(e){return e?oe:se},..._},le=W.forwardRef(function(e,t){let{render:n,className:r,defaultOpen:a=!1,disabled:o=!1,onOpenChange:s,open:c,style:l,...u}=e,d=p(s),f=ie({open:c,defaultOpen:a,onOpenChange:d,disabled:o}),m=W.useMemo(()=>({open:f.open,disabled:f.disabled,transitionStatus:f.transitionStatus}),[f.open,f.disabled,f.transitionStatus]),h=W.useMemo(()=>({...f,onOpenChange:d,state:m}),[f,d,m]),g=i(`div`,e,{state:m,ref:t,props:u,stateAttributesMapping:Y});return(0,G.jsx)(K.Provider,{value:h,children:g})}),ue={...ce,..._},de=W.forwardRef(function(e,t){let{panelId:n,open:r,handleTrigger:o,state:s,disabled:c}=q(),{className:l,disabled:u=c,render:d,nativeButton:f=!0,style:p,...m}=e,{getButtonProps:h,buttonRef:g}=a({disabled:u,focusableWhenDisabled:!0,native:f});return i(`button`,e,{state:s,ref:[t,g],props:[{"aria-controls":r?n:void 0,"aria-expanded":r,onClick:o},m,h],stateAttributesMapping:ue})}),X={height:void 0,width:void 0};function fe(e){let{externalRef:t,hiddenUntilFound:n,id:i,keepMounted:a,mounted:o,onOpenChange:s,open:l,setMounted:u,setOpen:d,transitionStatus:f}=e,h=W.useRef(null),_=W.useRef(null),[v,b]=W.useState(X),x=W.useRef(X),S=W.useRef(!1),D=W.useRef(l),O=W.useRef(!1),[k,A]=W.useState(!1),j=W.useRef(null),M=c(t,h),N=C(l),P=w(h),F=!l&&!o,I=k?`idle`:f,L=l&&(D.current||O.current),R=!l&&o&&_.current===`css-animation`&&v.height===void 0&&v.width===void 0?x.current:v,z=n&&F&&_.current!==`css-animation`,B=p((e,t=!0)=>{t&&(x.current=e),b(e)}),V=p(()=>{j.current?.(),j.current=null}),H=p(e=>{V(),j.current=()=>{j.current=null,e()}}),U=p(()=>{l&&o&&_.current===`css-animation`&&(O.current=!0)});r(()=>{!k||f===`starting`||A(!1)},[k,f]),W.useEffect(()=>()=>{U(),V()},[U,V]),r(()=>{let e=h.current;if(!e)return;!l&&j.current&&V();let t=pe(e,L);if(_.current=t,l&&f===`idle`&&D.current&&t===`css-animation`){x.current=Z(e);return}if(l&&f===`starting`){let n=S.current;if(S.current=!1,t===`none`){B(Z(e)),A(!0);return}if(t===`css-transition`){let t=he(e);if(B(Z(e)),!n)return t;let r=Q(e,`transition-duration`,`0s`);return H(r),A(!0),t}B(Z(e));let r=Q(e,`animation-name`,`none`);if(!n){r();return}let i=Q(e,`animation-duration`,`0s`);r(),H(i),A(!0);return}if(!l&&o&&(f===`idle`||f===`starting`)){if(D.current=!1,O.current=!1,t===`none`){B(X,!1),u(!1);return}B(Z(e));return}if(f!==`ending`)return;if(t===`none`){u(!1);return}let n=Z(e);if(!(n.height>0||n.width>0)){u(!1);return}B(n),t===`css-animation`&&Q(e,`animation-name`,`none`)()},[o,l,V,B,u,H,L,f]),E({enabled:l&&o&&I===`idle`,open:!0,ref:h,onComplete(){l&&B(X,!1)}}),W.useEffect(()=>{if(l||!o||I!==`ending`||!h.current)return;let e=new AbortController,t=-1;function n(){N.current||(u(!1),B(X,!1))}return t=y.request(()=>{P(n,e.signal)}),()=>{y.cancel(t),e.abort()}},[N,o,l,I,P,B,u]),r(()=>{let e=h.current;!e||!n||!F||e.setAttribute(`hidden`,`until-found`)},[F,n]),W.useEffect(function(){let e=h.current;if(!e)return;function t(e){let t=m(g,e);s(!0,t),!t.isCanceled&&(S.current=!0,d(!0))}return T(e,`beforematch`,t)},[s,d]);let ee=a||n||o||l;return{height:R.height,props:{...z?{[J.startingStyle]:``}:void 0,hidden:F,id:i},ref:M,shouldPreventOpenAnimation:L,shouldRender:ee,transitionStatus:I,width:R.width}}function Z(e){return{height:e.scrollHeight,width:e.scrollWidth}}function pe(e,n){let r=t(e).getComputedStyle(e),i=(r.animationName.split(`,`).map(e=>e.trim()).some(e=>e!==``&&e!==`none`)||n)&&me(r.animationDuration),a=me(r.transitionDuration);return i&&a||a?`css-transition`:i?`css-animation`:`none`}function me(e){return e.split(`,`).map(e=>e.trim()).some(e=>e!==``&&Number.parseFloat(e)>0)}function Q(e,t,n){let r=e.style.getPropertyValue(t),i=e.style.getPropertyPriority(t);return e.style.setProperty(t,n),()=>{if(r===``){e.style.removeProperty(t);return}e.style.setProperty(t,r,i)}}function he(e){let t={"justify-content":e.style.justifyContent,"align-items":e.style.alignItems,"align-content":e.style.alignContent,"justify-items":e.style.justifyItems};Object.keys(t).forEach(t=>{e.style.setProperty(t,`initial`,`important`)});function n(){Object.entries(t).forEach(([t,n])=>{if(n===``){e.style.removeProperty(t);return}e.style.setProperty(t,n)})}let r=y.request(n);return()=>{y.cancel(r),n()}}var ge=function(e){return e.collapsiblePanelHeight=`--collapsible-panel-height`,e.collapsiblePanelWidth=`--collapsible-panel-width`,e}({}),_e=v(W.forwardRef(function(e,t){let{className:n,hiddenUntilFound:a,keepMounted:o,render:c,id:l,style:u,...d}=e,{defaultPanelId:f,mounted:p,onOpenChange:m,open:h,setMounted:g,setPanelIdState:_,setOpen:v,state:y,transitionStatus:b}=q(),x=a??!1,S=o??!1,C=l||void 0,w=C??f;r(()=>(_(e=>C??(e===null?void 0:e)),()=>{_(e=>e===C?null:e)}),[C,_]);let{height:T,props:E,ref:D,shouldPreventOpenAnimation:O,shouldRender:k,transitionStatus:A,width:j}=fe({externalRef:t,hiddenUntilFound:x,id:w,keepMounted:S,mounted:p,onOpenChange:m,open:h,setMounted:g,setOpen:v,transitionStatus:b}),M={...y,transitionStatus:A},N=s(u,M),P=i(`div`,{...e,style:void 0},{state:M,ref:D,props:[E,{style:{[ge.collapsiblePanelHeight]:T===void 0?`auto`:`${T}px`,[ge.collapsiblePanelWidth]:j===void 0?`auto`:`${j}px`}},d,N?{style:N}:void 0,O?{style:{animationName:`none`}}:void 0],stateAttributesMapping:Y});return k?P:null}),`tr-collapsible-content`),$={Root:v(le,`tr-collapsible`),Trigger:v(de,`tr-collapsible-summary`),Panel:_e};function ve({setupData:e,onNext:t,additionalActions:n,className:r=``}){let{t:i}=o();return(0,G.jsxs)(`div`,{className:`flex flex-col gap-tinyrack-lg ${r}`,children:[(0,G.jsx)(`p`,{className:`text-center text-tinyrack-sm text-tinyrack-text-muted`,children:i(`setupTotp.qrDescription`)}),(0,G.jsx)(`div`,{className:`flex justify-center`,children:(0,G.jsx)(`div`,{className:`rounded-tinyrack-lg border border-tinyrack-border bg-tinyrack-surface p-tinyrack-lg`,children:(0,G.jsx)(`img`,{alt:`TOTP QR Code`,className:`block size-tinyrack-measure-sm`,src:e.qr_code})})}),(0,G.jsxs)($.Root,{children:[(0,G.jsx)($.Trigger,{children:i(`setupTotp.manualEntry`)}),(0,G.jsx)($.Panel,{children:(0,G.jsx)(`code`,{className:`mt-tinyrack-xs block break-all rounded-tinyrack-sm bg-tinyrack-surface-muted p-tinyrack-sm text-tinyrack-sm text-tinyrack-text`,children:e.secret})})]}),(0,G.jsx)(u,{className:`w-full`,"data-testid":`totp-qr-next`,intent:`primary`,onClick:t,type:`button`,uiSize:`lg`,children:i(`setupTotp.next`)}),n]})}function ye(e={}){let{onSetupSuccess:t,onSetupError:n,onVerifySuccess:r,onVerifyError:i,onConfirmSuccess:a,onConfirmError:o,autoStart:s=!1}=e,c=h(),[l,u]=(0,W.useState)(s?`loading`:`qr`),[d,f]=(0,W.useState)(null),[p,m]=(0,W.useState)([]),g=(0,W.useRef)(!1),_=D({...B,onSuccess:e=>{f(e),u(`qr`),t?.(e)},onError:e=>{u(`error`),n?.(e)}}),v=D({...V,onSuccess:e=>{m(e.recovery_codes),u(`recovery`),r?.(e)},onError:e=>{i?.(e)}}),y=D({...H,onSuccess:e=>{c.invalidateQueries({queryKey:P.queryKey}),a?.(e)},onError:e=>{o?.(e)}}),b=(0,W.useCallback)(()=>{g.current=!0,u(`loading`),_.mutate()},[_]),x=(0,W.useCallback)(async e=>v.mutateAsync({code:e}),[v]),S=(0,W.useCallback)(()=>{u(`qr`)},[]),C=(0,W.useCallback)(()=>{u(`verify`)},[]),w=(0,W.useCallback)(async()=>y.mutateAsync(),[y]),T=(0,W.useCallback)(()=>{u(s?`loading`:`qr`),f(null),m([]),g.current=!1,_.reset(),v.reset(),y.reset()},[s,_,v,y]);return(0,W.useEffect)(()=>{s&&!g.current&&l===`loading`&&(g.current=!0,_.mutate())},[s,l,_]),{step:l,setupData:d,recoveryCodes:p,isSetupPending:_.isPending,isVerifyPending:v.isPending,isConfirmPending:y.isPending,isPending:_.isPending||v.isPending||y.isPending,setupError:_.error,verifyError:v.error,confirmError:y.error,startSetup:b,verify:x,goToQr:S,goToVerify:C,confirmRecoveryCodes:w,reset:T}}export{te as a,ne as i,ve as n,re as r,ye as t};
3
+ //# sourceMappingURL=use-totp-setup-CtTaklH-.js.map