@tinyrack/tinyauth-server 0.6.0 → 0.7.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 (107) hide show
  1. package/dist/entrypoints/app.d.ts +1 -1
  2. package/dist/routes/api/consent/index.d.ts +1 -1
  3. package/dist/routes/api/consent/post.d.ts +1 -1
  4. package/dist/routes/api/consent/post.d.ts.map +1 -1
  5. package/dist/routes/api/consent/post.js +9 -1
  6. package/dist/routes/api/consent/post.js.map +1 -1
  7. package/dist/routes/api/index.d.ts +1 -1
  8. package/dist/routes/index.d.ts +1 -1
  9. package/dist/routes/oauth/authorize/get.d.ts.map +1 -1
  10. package/dist/routes/oauth/authorize/get.js +9 -1
  11. package/dist/routes/oauth/authorize/get.js.map +1 -1
  12. package/dist/services/account-selection.service.d.ts.map +1 -1
  13. package/dist/services/account-selection.service.js +3 -1
  14. package/dist/services/account-selection.service.js.map +1 -1
  15. package/dist/services/oauth-authorize.service.d.ts.map +1 -1
  16. package/dist/services/oauth-authorize.service.js +15 -15
  17. package/dist/services/oauth-authorize.service.js.map +1 -1
  18. package/package.json +1 -1
  19. package/public/assets/2fa-BMEIiDXv.js +2 -0
  20. package/public/assets/{2fa-SSKfXB7c.js.map → 2fa-BMEIiDXv.js.map} +1 -1
  21. package/public/assets/{2fa-BoyBKrjD.js → 2fa-BhCgCfEc.js} +2 -2
  22. package/public/assets/{2fa-BoyBKrjD.js.map → 2fa-BhCgCfEc.js.map} +1 -1
  23. package/public/assets/2fa-Cteolf9l.js +2 -0
  24. package/public/assets/{2fa-IkQlgUP0.js.map → 2fa-Cteolf9l.js.map} +1 -1
  25. package/public/assets/{2fa-DfWvDjDW.js → 2fa-E0x3Samn.js} +2 -2
  26. package/public/assets/{2fa-DfWvDjDW.js.map → 2fa-E0x3Samn.js.map} +1 -1
  27. package/public/assets/{admin-D2CMlWzS.js → admin-CXJ0z2QN.js} +2 -2
  28. package/public/assets/{admin-D2CMlWzS.js.map → admin-CXJ0z2QN.js.map} +1 -1
  29. package/public/assets/{consent-DwuWkp63.js → consent-B17hGrta.js} +2 -2
  30. package/public/assets/{consent-DwuWkp63.js.map → consent-B17hGrta.js.map} +1 -1
  31. package/public/assets/{consent-C5Qo0iLd.js → consent-C-h_pWHQ.js} +2 -2
  32. package/public/assets/{consent-C5Qo0iLd.js.map → consent-C-h_pWHQ.js.map} +1 -1
  33. package/public/assets/email-BMEIiDXv.js +2 -0
  34. package/public/assets/{email-SSKfXB7c.js.map → email-BMEIiDXv.js.map} +1 -1
  35. package/public/assets/{email-CIttZRBe.js → email-DjE7mqnX.js} +2 -2
  36. package/public/assets/{email-CIttZRBe.js.map → email-DjE7mqnX.js.map} +1 -1
  37. package/public/assets/{error-D60wkdWN.js → error-BKXsXhdR.js} +2 -2
  38. package/public/assets/{error-D60wkdWN.js.map → error-BKXsXhdR.js.map} +1 -1
  39. package/public/assets/forgot-BMEIiDXv.js +2 -0
  40. package/public/assets/{forgot-SSKfXB7c.js.map → forgot-BMEIiDXv.js.map} +1 -1
  41. package/public/assets/{forgot-x-UDyHXT.js → forgot-C9zSfaX9.js} +2 -2
  42. package/public/assets/{forgot-x-UDyHXT.js.map → forgot-C9zSfaX9.js.map} +1 -1
  43. package/public/assets/{index-CsT6OVnP.js → index-BmfaaNx6.js} +3 -3
  44. package/public/assets/index-BmfaaNx6.js.map +1 -0
  45. package/public/assets/login-BMEIiDXv.js +2 -0
  46. package/public/assets/login-BMEIiDXv.js.map +1 -0
  47. package/public/assets/login-DG1Yfmb8.js +2 -0
  48. package/public/assets/login-DG1Yfmb8.js.map +1 -0
  49. package/public/assets/{passkey-BdISbWr7.js → passkey-BDrX3jVg.js} +2 -2
  50. package/public/assets/{passkey-BdISbWr7.js.map → passkey-BDrX3jVg.js.map} +1 -1
  51. package/public/assets/{passkey-Bv7zPLAZ.js → passkey-C-h8Fvqn.js} +2 -2
  52. package/public/assets/{passkey-Bv7zPLAZ.js.map → passkey-C-h8Fvqn.js.map} +1 -1
  53. package/public/assets/password-86C-hCze.js +2 -0
  54. package/public/assets/password-86C-hCze.js.map +1 -0
  55. package/public/assets/password-BMEIiDXv.js +2 -0
  56. package/public/assets/password-BMEIiDXv.js.map +1 -0
  57. package/public/assets/{profile-D2cuVYgE.js → profile-8buOY6GT.js} +2 -2
  58. package/public/assets/{profile-D2cuVYgE.js.map → profile-8buOY6GT.js.map} +1 -1
  59. package/public/assets/{profile-TKdT20x5.js → profile-DcpL_XUt.js} +2 -2
  60. package/public/assets/{profile-TKdT20x5.js.map → profile-DcpL_XUt.js.map} +1 -1
  61. package/public/assets/{recovery-DM8h2gbb.js → recovery-Csf6dtvj.js} +2 -2
  62. package/public/assets/{recovery-DM8h2gbb.js.map → recovery-Csf6dtvj.js.map} +1 -1
  63. package/public/assets/register-BMEIiDXv.js +2 -0
  64. package/public/assets/{register-SSKfXB7c.js.map → register-BMEIiDXv.js.map} +1 -1
  65. package/public/assets/{register-vWW_43cD.js → register-X6dycne4.js} +2 -2
  66. package/public/assets/{register-vWW_43cD.js.map → register-X6dycne4.js.map} +1 -1
  67. package/public/assets/{reset-CgACYrdp.js → reset-1nsqPlxL.js} +2 -2
  68. package/public/assets/{reset-CgACYrdp.js.map → reset-1nsqPlxL.js.map} +1 -1
  69. package/public/assets/reset-BMEIiDXv.js +2 -0
  70. package/public/assets/{reset-SSKfXB7c.js.map → reset-BMEIiDXv.js.map} +1 -1
  71. package/public/assets/select-BMEIiDXv.js +2 -0
  72. package/public/assets/select-BMEIiDXv.js.map +1 -0
  73. package/public/assets/{select-BCP5fwfB.js → select-CDdb3jyc.js} +2 -2
  74. package/public/assets/select-CDdb3jyc.js.map +1 -0
  75. package/public/assets/{terms-DPWrbYY2.js → terms-CUhvMN9k.js} +2 -2
  76. package/public/assets/{terms-DPWrbYY2.js.map → terms-CUhvMN9k.js.map} +1 -1
  77. package/public/assets/{terms-TKdT20x5.js → terms-DcpL_XUt.js} +2 -2
  78. package/public/assets/{terms-TKdT20x5.js.map → terms-DcpL_XUt.js.map} +1 -1
  79. package/public/assets/{totp-CKZ6N1NS.js → totp-DB9WqW4V.js} +2 -2
  80. package/public/assets/{totp-CKZ6N1NS.js.map → totp-DB9WqW4V.js.map} +1 -1
  81. package/public/assets/{totp-D-PVOsGQ.js → totp-UDT2P91H.js} +2 -2
  82. package/public/assets/{totp-D-PVOsGQ.js.map → totp-UDT2P91H.js.map} +1 -1
  83. package/public/assets/{use-totp-setup-BH75uEbE.js → use-totp-setup-9fktn_he.js} +2 -2
  84. package/public/assets/{use-totp-setup-BH75uEbE.js.map → use-totp-setup-9fktn_he.js.map} +1 -1
  85. package/public/assets/{useMutation-DVMopbtG.js → useMutation-Iu4AJCB4.js} +2 -2
  86. package/public/assets/{useMutation-DVMopbtG.js.map → useMutation-Iu4AJCB4.js.map} +1 -1
  87. package/public/assets/{users-B7ofdp72.js → users-Bi7gjreq.js} +2 -2
  88. package/public/assets/{users-B7ofdp72.js.map → users-Bi7gjreq.js.map} +1 -1
  89. package/public/index.html +1 -1
  90. package/public/assets/2fa-IkQlgUP0.js +0 -2
  91. package/public/assets/2fa-SSKfXB7c.js +0 -2
  92. package/public/assets/email-SSKfXB7c.js +0 -2
  93. package/public/assets/forgot-SSKfXB7c.js +0 -2
  94. package/public/assets/index-CsT6OVnP.js.map +0 -1
  95. package/public/assets/login-DhbnCudI.js +0 -2
  96. package/public/assets/login-DhbnCudI.js.map +0 -1
  97. package/public/assets/login-SSKfXB7c.js +0 -2
  98. package/public/assets/login-SSKfXB7c.js.map +0 -1
  99. package/public/assets/password-CkeV4qxb.js +0 -2
  100. package/public/assets/password-CkeV4qxb.js.map +0 -1
  101. package/public/assets/password-SSKfXB7c.js +0 -2
  102. package/public/assets/password-SSKfXB7c.js.map +0 -1
  103. package/public/assets/register-SSKfXB7c.js +0 -2
  104. package/public/assets/reset-SSKfXB7c.js +0 -2
  105. package/public/assets/select-BCP5fwfB.js.map +0 -1
  106. package/public/assets/select-SSKfXB7c.js +0 -2
  107. package/public/assets/select-SSKfXB7c.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"admin-D2CMlWzS.js","names":["useSuspenseQueries","useSuspenseQuery","Link","useTranslation","AdminDisabledPanel","AdminShell","PageLayout","adminUsersQueryOptions","appConfigQueryOptions","Route","AdminDashboard","t","user","useRouteContext","select","context","role","AdminDashboardGate","SessionUser","data","config","admin","enabled","AdminDashboardContent","usersQuery","adminQuery","configQuery","databaseQuery","queries","pageSize","managedBy","configUsers","pagination","total","databaseUsers","admins","users","slice","map","managedUser","sub","email","formatAdminRole","formatManagedBy","managed_by","email_verified","ReturnType","MetricStat","label","value","hint","accent","PolicyRow","component"],"sources":["../../../frontend/src/routes/admin/index.tsx?tsr-split=component"],"sourcesContent":["import { useSuspenseQueries, useSuspenseQuery } from '@tanstack/react-query';\nimport { createFileRoute, Link, redirect } from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { AdminDisabledPanel } from '#frontend/features/admin/admin-disabled-panel.tsx';\nimport { AdminShell } from '#frontend/features/admin/admin-shell.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport { adminUsersQueryOptions } from '#frontend/queries/admin-users.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport type { SessionUser } from '#frontend/queries/session.ts';\n\nexport const Route = createFileRoute('/admin/')({\n component: AdminDashboard,\n beforeLoad: async ({ context }) => {\n if (!context.user) {\n throw redirect({\n to: '/login',\n });\n }\n },\n});\n\nfunction AdminDashboard() {\n const { t } = useTranslation();\n const user = Route.useRouteContext({ select: (context) => context.user });\n\n if (user?.role !== 'admin') {\n return (\n <PageLayout cardPadding maxWidth=\"md\">\n <h1 className=\"mb-2 text-center font-bold text-2xl\">\n {t('admin.accessRequired')}\n </h1>\n <p className=\"text-center text-base-content/60\">\n {t('admin.accessRequiredDescription')}\n </p>\n </PageLayout>\n );\n }\n\n return <AdminDashboardGate user={user} />;\n}\n\nfunction AdminDashboardGate({ user }: { user: SessionUser }) {\n const { data: config } = useSuspenseQuery(appConfigQueryOptions);\n if (!config.admin.enabled) {\n return <AdminDisabledPanel />;\n }\n return <AdminDashboardContent user={user} />;\n}\n\nfunction AdminDashboardContent({ user }: { user: SessionUser }) {\n const { t } = useTranslation();\n const [usersQuery, adminQuery, configQuery, databaseQuery] =\n useSuspenseQueries({\n queries: [\n adminUsersQueryOptions(),\n adminUsersQueryOptions({ pageSize: 1, role: 'admin' }),\n adminUsersQueryOptions({ managedBy: 'config', pageSize: 1 }),\n adminUsersQueryOptions({ managedBy: 'database', pageSize: 1 }),\n ],\n });\n const data = usersQuery.data;\n const configUsers = configQuery.data.pagination.total;\n const databaseUsers = databaseQuery.data.pagination.total;\n const admins = adminQuery.data.pagination.total;\n\n return (\n <AdminShell\n current=\"dashboard\"\n description={t('admin.dashboardDescription')}\n title={t('admin.dashboardTitle')}\n user={user}\n >\n <div className=\"stats stats-vertical lg:stats-horizontal w-full overflow-hidden rounded-[1.5rem] border border-white/10 bg-white/[0.035] shadow-[0_24px_80px_rgba(0,0,0,0.24)] backdrop-blur-xl\">\n <MetricStat\n accent=\"from-[#7170ff] to-[#9da3ff]\"\n hint={t('admin.metrics.totalUsersHint')}\n label={t('admin.metrics.totalUsers')}\n value={data.pagination.total}\n />\n <MetricStat\n accent=\"from-emerald-400 to-teal-300\"\n hint={t('admin.metrics.adminsHint')}\n label={t('admin.metrics.admins')}\n value={admins}\n />\n <MetricStat\n accent=\"from-sky-400 to-cyan-300\"\n hint={t('admin.metrics.databaseUsersHint')}\n label={t('admin.metrics.databaseUsers')}\n value={databaseUsers}\n />\n </div>\n\n <div className=\"mt-6 grid gap-6 xl:grid-cols-[1.35fr_0.75fr]\">\n <section className=\"card overflow-hidden border border-white/10 bg-white/[0.035] shadow-[0_24px_80px_rgba(0,0,0,0.22)] backdrop-blur-xl\">\n <div className=\"card-body p-0\">\n <div className=\"flex flex-col gap-4 border-white/10 border-b p-6 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <div className=\"mb-2 flex items-center gap-2\">\n <span className=\"h-2 w-2 rounded-full bg-[#7170ff] shadow-[0_0_18px_rgba(113,112,255,0.8)]\" />\n <span className=\"font-medium text-slate-500 text-xs uppercase tracking-[0.18em]\">\n {t('admin.identityGraph')}\n </span>\n </div>\n <h2 className=\"card-title text-slate-100\">\n {t('admin.users.title')}\n </h2>\n <p className=\"mt-1 text-slate-400 text-sm\">\n {t('admin.users.description')}\n </p>\n </div>\n <Link\n className=\"btn btn-primary btn-sm border-0 bg-[#7170ff] text-white shadow-[0_14px_40px_rgba(113,112,255,0.32)] hover:bg-[#828fff]\"\n to=\"/admin/users\"\n >\n {t('admin.users.open')}\n </Link>\n </div>\n\n <div className=\"grid gap-0 divide-y divide-white/10 md:grid-cols-2 md:divide-x md:divide-y-0\">\n {data.users.slice(0, 4).map((managedUser) => (\n <div\n className=\"group bg-white/[0.015] p-5 transition hover:bg-white/[0.045]\"\n key={managedUser.sub}\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-slate-100 text-sm\">\n {managedUser.email}\n </p>\n <p className=\"mt-1 truncate font-mono text-slate-600 text-xs\">\n {managedUser.sub}\n </p>\n </div>\n <span\n className={\n managedUser.role === 'admin'\n ? 'badge badge-primary border-[#7170ff]/30 bg-[#7170ff]/20 text-[#c7c8ff]'\n : 'badge badge-outline border-white/15 text-slate-400'\n }\n >\n {formatAdminRole(t, managedUser.role)}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-between text-xs\">\n <span className=\"badge badge-ghost border-white/10 bg-white/[0.035] text-slate-400\">\n {formatManagedBy(t, managedUser.managed_by)}\n </span>\n <span className=\"text-slate-500\">\n {t('admin.users.verifiedLabel')}{' '}\n {managedUser.email_verified\n ? t('common.yes')\n : t('common.no')}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n </section>\n\n <section className=\"card overflow-hidden border border-[#7170ff]/20 bg-[linear-gradient(145deg,rgba(113,112,255,0.18),rgba(255,255,255,0.035)_45%,rgba(16,185,129,0.10))] text-slate-100 shadow-[0_24px_80px_rgba(0,0,0,0.25)] backdrop-blur-xl\">\n <div className=\"card-body\">\n <div className=\"badge badge-primary badge-outline border-[#7170ff]/40 bg-[#7170ff]/10 text-[#c7c8ff]\">\n {t('admin.policy.title')}\n </div>\n <h2 className=\"card-title mt-3 text-2xl tracking-[-0.03em]\">\n {t('admin.policy.heading')}\n </h2>\n <p className=\"mt-1 text-slate-400 text-sm\">\n {t('admin.policy.description')}\n </p>\n <div className=\"mt-6 space-y-3\">\n <PolicyRow\n label={t('admin.metrics.configUsers')}\n value={configUsers}\n />\n <PolicyRow\n label={t('admin.metrics.databaseUsers')}\n value={databaseUsers}\n />\n <PolicyRow\n label={t('admin.policy.configReadonly')}\n value={t('common.on')}\n />\n </div>\n </div>\n </section>\n </div>\n </AdminShell>\n );\n}\n\nfunction formatAdminRole(\n t: ReturnType<typeof useTranslation>['t'],\n role: SessionUser['role'],\n) {\n return role === 'admin'\n ? t('admin.users.roleAdmin')\n : t('admin.users.roleUser');\n}\n\nfunction formatManagedBy(\n t: ReturnType<typeof useTranslation>['t'],\n managedBy: 'database' | 'config',\n) {\n return managedBy === 'database'\n ? t('admin.users.sourceDatabase')\n : t('admin.users.sourceConfig');\n}\n\nfunction MetricStat({\n label,\n value,\n hint,\n accent,\n}: {\n label: string;\n value: number;\n hint: string;\n accent: string;\n}) {\n return (\n <div className=\"stat border-white/10 bg-transparent p-6\">\n <div\n className={`mb-4 h-1.5 w-16 rounded-full bg-gradient-to-r ${accent}`}\n />\n <div className=\"stat-title text-slate-500\">{label}</div>\n <div className=\"stat-value mt-1 text-slate-50 tracking-[-0.06em]\">\n {value}\n </div>\n <div className=\"stat-desc mt-2 text-slate-500\">{hint}</div>\n </div>\n );\n}\n\nfunction PolicyRow({\n label,\n value,\n}: {\n label: string;\n value: number | string;\n}) {\n return (\n <div className=\"flex items-center justify-between rounded-2xl border border-white/10 bg-black/20 px-4 py-3\">\n <span className=\"text-slate-300\">{label}</span>\n <span className=\"badge badge-primary border-[#7170ff]/30 bg-[#7170ff]/20 text-[#d7d8ff]\">\n {value}\n </span>\n </div>\n );\n}\n"],"mappings":"kRAqBA,SAASU,GAAiB,CACxB,GAAM,CAAEC,KAAMR,EAAe,EACvBS,EAAOH,EAAMI,gBAAgB,CAAEC,OAASC,GAAYA,EAAQH,IAAK,CAAC,EAexE,OAbIA,GAAMI,OAAS,SAaZ,EAAA,EAAA,KAAC,EAAD,CAA0BJ,MAAK,CAAA,GAXlC,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,cAAjC,EACE,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,+CACXD,EAAE,sBAAsB,CACvB,CAAA,GACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,4CACVA,EAAE,iCAAiC,CACnC,CAAA,CACO,GAKlB,CAEA,SAASM,EAAmB,CAAEL,QAA+B,CAC3D,GAAM,CAAEO,KAAMC,GAAWnB,EAAiBO,CAAqB,EAI/D,OAHKY,EAAOC,MAAMC,SAGX,EAAA,EAAA,KAAC,EAAD,CAA6BV,MAAK,CAAA,GAFhC,EAAA,EAAA,KAAC,EAAD,CAAmB,CAAA,CAG9B,CAEA,SAASW,EAAsB,CAAEX,QAA+B,CAC9D,GAAM,CAAED,KAAMR,EAAe,EACvB,CAACqB,EAAYC,EAAYC,EAAaC,GAC1C3B,EAAmB,CACjB4B,QAAS,CACPrB,EAAuB,EACvBA,EAAuB,CAAEsB,SAAU,EAAGb,KAAM,OAAQ,CAAC,EACrDT,EAAuB,CAAEuB,UAAW,SAAUD,SAAU,CAAE,CAAC,EAC3DtB,EAAuB,CAAEuB,UAAW,WAAYD,SAAU,CAAE,CAAC,CAAC,CAElE,CAAC,EACGV,EAAOK,EAAWL,KAClBY,EAAcL,EAAYP,KAAKa,WAAWC,MAC1CC,EAAgBP,EAAcR,KAAKa,WAAWC,MAC9CE,EAASV,EAAWN,KAAKa,WAAWC,MAE1C,OACE,EAAA,EAAA,MAAC,EAAD,CACE,QAAQ,YACR,YAAatB,EAAE,4BAA4B,EAC3C,MAAOA,EAAE,sBAAsB,EACzBC,gBAJR,EAME,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2LAAf,EACE,EAAA,EAAA,KAAC,EAAD,CACE,OAAO,8BACP,KAAMD,EAAE,8BAA8B,EACtC,MAAOA,EAAE,0BAA0B,EACnC,MAAOQ,EAAKa,WAAWC,KAAM,CAAA,GAE/B,EAAA,EAAA,KAAC,EAAD,CACE,OAAO,+BACP,KAAMtB,EAAE,0BAA0B,EAClC,MAAOA,EAAE,sBAAsB,EAC/B,MAAOwB,CAAO,CAAA,GAEhB,EAAA,EAAA,KAAC,EAAD,CACE,OAAO,2BACP,KAAMxB,EAAE,iCAAiC,EACzC,MAAOA,EAAE,6BAA6B,EACtC,MAAOuB,CAAc,CAAA,CAEpB,KAEL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wDAAf,EACE,EAAA,EAAA,KAAC,UAAD,CAAS,UAAU,gIACjB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,yBAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2GAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAA,SAAA,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wCAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,2EAA2E,CAAA,GAC3F,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,0EACbvB,EAAE,qBAAqB,CACpB,CAAA,CACH,KACL,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,qCACXA,EAAE,mBAAmB,CACpB,CAAA,GACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,uCACVA,EAAE,yBAAyB,CAC3B,CAAA,CACA,CAAA,CAAA,GACL,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,yHACV,GAAG,wBAEFA,EAAE,kBAAkB,CACjB,CAAA,CACH,KAEL,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,wFACZQ,EAAKiB,MAAMC,MAAM,EAAG,CAAC,EAAEC,IAAKC,IAC3B,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,wEADZ,EAIE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kDAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mBAAf,EACE,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,uDACVA,EAAYE,KACZ,CAAA,GACH,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,0DACVF,EAAYC,GACZ,CAAA,CACA,KACL,EAAA,EAAA,KAAC,OAAD,CACE,UACED,EAAYvB,OAAS,QACjB,yEACA,8DAGL0B,EAAgB/B,EAAG4B,EAAYvB,IAAI,CAChC,CAAA,CACH,KACL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0DAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,6EACb2B,EAAgBhC,EAAG4B,EAAYK,UAAU,CACtC,CAAA,GACN,EAAA,EAAA,MAAC,OAAD,CAAM,UAAU,0BAAhB,CACGjC,EAAE,2BAA2B,EAAG,IAChC4B,EAAYM,eACTlC,EAAE,YAAY,EACdA,EAAE,WAAW,CACb,GACH,GACF,GAhCE4B,EAAYC,GAgCd,CACN,CACE,CAAA,CACF,GACE,CAAA,GAET,EAAA,EAAA,KAAC,UAAD,CAAS,UAAU,wOACjB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAf,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,gGACZ7B,EAAE,oBAAoB,CACpB,CAAA,GACL,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,uDACXA,EAAE,sBAAsB,CACvB,CAAA,GACJ,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,uCACVA,EAAE,0BAA0B,CAC5B,CAAA,GACH,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,0BAAf,EACE,EAAA,EAAA,KAAC,EAAD,CACE,MAAOA,EAAE,2BAA2B,EACpC,MAAOoB,CAAY,CAAA,GAErB,EAAA,EAAA,KAAC,EAAD,CACE,MAAOpB,EAAE,6BAA6B,EACtC,MAAOuB,CAAc,CAAA,GAEvB,EAAA,EAAA,KAAC,EAAD,CACE,MAAOvB,EAAE,6BAA6B,EACtC,MAAOA,EAAE,WAAW,CAAE,CAAA,CAErB,GACF,GACE,CAAA,CACN,GACK,GAEhB,CAEA,SAAS+B,EACP/B,EACAK,EACA,CACA,OACIL,EADGK,IAAS,QACV,wBACA,sBAAsB,CAC9B,CAEA,SAAS2B,EACPhC,EACAmB,EACA,CACA,OACInB,EADGmB,IAAc,WACf,6BACA,0BAA0B,CAClC,CAEA,SAASiB,EAAW,CAClBC,QACAC,QACAC,OACAC,UAMC,CACD,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mDAAf,EACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAW,iDAAiDA,GAAS,CAAA,GAEvE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,qCAA6BH,CAAW,CAAA,GACvD,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,4DACZC,CACE,CAAA,GACL,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,yCAAiCC,CAAU,CAAA,CACvD,GAET,CAEA,SAASE,EAAU,CACjBJ,QACAC,SAIC,CACD,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,sGAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,0BAAkBD,CAAY,CAAA,GAC9C,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,kFACbC,CACG,CAAA,CACH,GAET"}
1
+ {"version":3,"file":"admin-CXJ0z2QN.js","names":["useSuspenseQueries","useSuspenseQuery","Link","useTranslation","AdminDisabledPanel","AdminShell","PageLayout","adminUsersQueryOptions","appConfigQueryOptions","Route","AdminDashboard","t","user","useRouteContext","select","context","role","AdminDashboardGate","SessionUser","data","config","admin","enabled","AdminDashboardContent","usersQuery","adminQuery","configQuery","databaseQuery","queries","pageSize","managedBy","configUsers","pagination","total","databaseUsers","admins","users","slice","map","managedUser","sub","email","formatAdminRole","formatManagedBy","managed_by","email_verified","ReturnType","MetricStat","label","value","hint","accent","PolicyRow","component"],"sources":["../../../frontend/src/routes/admin/index.tsx?tsr-split=component"],"sourcesContent":["import { useSuspenseQueries, useSuspenseQuery } from '@tanstack/react-query';\nimport { createFileRoute, Link, redirect } from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { AdminDisabledPanel } from '#frontend/features/admin/admin-disabled-panel.tsx';\nimport { AdminShell } from '#frontend/features/admin/admin-shell.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport { adminUsersQueryOptions } from '#frontend/queries/admin-users.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport type { SessionUser } from '#frontend/queries/session.ts';\n\nexport const Route = createFileRoute('/admin/')({\n component: AdminDashboard,\n beforeLoad: async ({ context }) => {\n if (!context.user) {\n throw redirect({\n to: '/login',\n });\n }\n },\n});\n\nfunction AdminDashboard() {\n const { t } = useTranslation();\n const user = Route.useRouteContext({ select: (context) => context.user });\n\n if (user?.role !== 'admin') {\n return (\n <PageLayout cardPadding maxWidth=\"md\">\n <h1 className=\"mb-2 text-center font-bold text-2xl\">\n {t('admin.accessRequired')}\n </h1>\n <p className=\"text-center text-base-content/60\">\n {t('admin.accessRequiredDescription')}\n </p>\n </PageLayout>\n );\n }\n\n return <AdminDashboardGate user={user} />;\n}\n\nfunction AdminDashboardGate({ user }: { user: SessionUser }) {\n const { data: config } = useSuspenseQuery(appConfigQueryOptions);\n if (!config.admin.enabled) {\n return <AdminDisabledPanel />;\n }\n return <AdminDashboardContent user={user} />;\n}\n\nfunction AdminDashboardContent({ user }: { user: SessionUser }) {\n const { t } = useTranslation();\n const [usersQuery, adminQuery, configQuery, databaseQuery] =\n useSuspenseQueries({\n queries: [\n adminUsersQueryOptions(),\n adminUsersQueryOptions({ pageSize: 1, role: 'admin' }),\n adminUsersQueryOptions({ managedBy: 'config', pageSize: 1 }),\n adminUsersQueryOptions({ managedBy: 'database', pageSize: 1 }),\n ],\n });\n const data = usersQuery.data;\n const configUsers = configQuery.data.pagination.total;\n const databaseUsers = databaseQuery.data.pagination.total;\n const admins = adminQuery.data.pagination.total;\n\n return (\n <AdminShell\n current=\"dashboard\"\n description={t('admin.dashboardDescription')}\n title={t('admin.dashboardTitle')}\n user={user}\n >\n <div className=\"stats stats-vertical lg:stats-horizontal w-full overflow-hidden rounded-[1.5rem] border border-white/10 bg-white/[0.035] shadow-[0_24px_80px_rgba(0,0,0,0.24)] backdrop-blur-xl\">\n <MetricStat\n accent=\"from-[#7170ff] to-[#9da3ff]\"\n hint={t('admin.metrics.totalUsersHint')}\n label={t('admin.metrics.totalUsers')}\n value={data.pagination.total}\n />\n <MetricStat\n accent=\"from-emerald-400 to-teal-300\"\n hint={t('admin.metrics.adminsHint')}\n label={t('admin.metrics.admins')}\n value={admins}\n />\n <MetricStat\n accent=\"from-sky-400 to-cyan-300\"\n hint={t('admin.metrics.databaseUsersHint')}\n label={t('admin.metrics.databaseUsers')}\n value={databaseUsers}\n />\n </div>\n\n <div className=\"mt-6 grid gap-6 xl:grid-cols-[1.35fr_0.75fr]\">\n <section className=\"card overflow-hidden border border-white/10 bg-white/[0.035] shadow-[0_24px_80px_rgba(0,0,0,0.22)] backdrop-blur-xl\">\n <div className=\"card-body p-0\">\n <div className=\"flex flex-col gap-4 border-white/10 border-b p-6 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <div className=\"mb-2 flex items-center gap-2\">\n <span className=\"h-2 w-2 rounded-full bg-[#7170ff] shadow-[0_0_18px_rgba(113,112,255,0.8)]\" />\n <span className=\"font-medium text-slate-500 text-xs uppercase tracking-[0.18em]\">\n {t('admin.identityGraph')}\n </span>\n </div>\n <h2 className=\"card-title text-slate-100\">\n {t('admin.users.title')}\n </h2>\n <p className=\"mt-1 text-slate-400 text-sm\">\n {t('admin.users.description')}\n </p>\n </div>\n <Link\n className=\"btn btn-primary btn-sm border-0 bg-[#7170ff] text-white shadow-[0_14px_40px_rgba(113,112,255,0.32)] hover:bg-[#828fff]\"\n to=\"/admin/users\"\n >\n {t('admin.users.open')}\n </Link>\n </div>\n\n <div className=\"grid gap-0 divide-y divide-white/10 md:grid-cols-2 md:divide-x md:divide-y-0\">\n {data.users.slice(0, 4).map((managedUser) => (\n <div\n className=\"group bg-white/[0.015] p-5 transition hover:bg-white/[0.045]\"\n key={managedUser.sub}\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-slate-100 text-sm\">\n {managedUser.email}\n </p>\n <p className=\"mt-1 truncate font-mono text-slate-600 text-xs\">\n {managedUser.sub}\n </p>\n </div>\n <span\n className={\n managedUser.role === 'admin'\n ? 'badge badge-primary border-[#7170ff]/30 bg-[#7170ff]/20 text-[#c7c8ff]'\n : 'badge badge-outline border-white/15 text-slate-400'\n }\n >\n {formatAdminRole(t, managedUser.role)}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-between text-xs\">\n <span className=\"badge badge-ghost border-white/10 bg-white/[0.035] text-slate-400\">\n {formatManagedBy(t, managedUser.managed_by)}\n </span>\n <span className=\"text-slate-500\">\n {t('admin.users.verifiedLabel')}{' '}\n {managedUser.email_verified\n ? t('common.yes')\n : t('common.no')}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n </section>\n\n <section className=\"card overflow-hidden border border-[#7170ff]/20 bg-[linear-gradient(145deg,rgba(113,112,255,0.18),rgba(255,255,255,0.035)_45%,rgba(16,185,129,0.10))] text-slate-100 shadow-[0_24px_80px_rgba(0,0,0,0.25)] backdrop-blur-xl\">\n <div className=\"card-body\">\n <div className=\"badge badge-primary badge-outline border-[#7170ff]/40 bg-[#7170ff]/10 text-[#c7c8ff]\">\n {t('admin.policy.title')}\n </div>\n <h2 className=\"card-title mt-3 text-2xl tracking-[-0.03em]\">\n {t('admin.policy.heading')}\n </h2>\n <p className=\"mt-1 text-slate-400 text-sm\">\n {t('admin.policy.description')}\n </p>\n <div className=\"mt-6 space-y-3\">\n <PolicyRow\n label={t('admin.metrics.configUsers')}\n value={configUsers}\n />\n <PolicyRow\n label={t('admin.metrics.databaseUsers')}\n value={databaseUsers}\n />\n <PolicyRow\n label={t('admin.policy.configReadonly')}\n value={t('common.on')}\n />\n </div>\n </div>\n </section>\n </div>\n </AdminShell>\n );\n}\n\nfunction formatAdminRole(\n t: ReturnType<typeof useTranslation>['t'],\n role: SessionUser['role'],\n) {\n return role === 'admin'\n ? t('admin.users.roleAdmin')\n : t('admin.users.roleUser');\n}\n\nfunction formatManagedBy(\n t: ReturnType<typeof useTranslation>['t'],\n managedBy: 'database' | 'config',\n) {\n return managedBy === 'database'\n ? t('admin.users.sourceDatabase')\n : t('admin.users.sourceConfig');\n}\n\nfunction MetricStat({\n label,\n value,\n hint,\n accent,\n}: {\n label: string;\n value: number;\n hint: string;\n accent: string;\n}) {\n return (\n <div className=\"stat border-white/10 bg-transparent p-6\">\n <div\n className={`mb-4 h-1.5 w-16 rounded-full bg-gradient-to-r ${accent}`}\n />\n <div className=\"stat-title text-slate-500\">{label}</div>\n <div className=\"stat-value mt-1 text-slate-50 tracking-[-0.06em]\">\n {value}\n </div>\n <div className=\"stat-desc mt-2 text-slate-500\">{hint}</div>\n </div>\n );\n}\n\nfunction PolicyRow({\n label,\n value,\n}: {\n label: string;\n value: number | string;\n}) {\n return (\n <div className=\"flex items-center justify-between rounded-2xl border border-white/10 bg-black/20 px-4 py-3\">\n <span className=\"text-slate-300\">{label}</span>\n <span className=\"badge badge-primary border-[#7170ff]/30 bg-[#7170ff]/20 text-[#d7d8ff]\">\n {value}\n </span>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{o as e}from"./IconBase.es-d5KP98Ac.js";import{A as t,N as n}from"./index-CsT6OVnP.js";var r=e();function i(e){return(0,r.jsx)(n,{...e,onUnauthorized:()=>{let e=new URLSearchParams(window.location.search),n=Object.fromEntries(e.entries());window.location.href=t(n)}})}export{i as errorComponent};
2
- //# sourceMappingURL=consent-DwuWkp63.js.map
1
+ import{o as e}from"./IconBase.es-d5KP98Ac.js";import{A as t,N as n}from"./index-BmfaaNx6.js";var r=e();function i(e){return(0,r.jsx)(n,{...e,onUnauthorized:()=>{let e=new URLSearchParams(window.location.search),n=Object.fromEntries(e.entries());window.location.href=t(n)}})}export{i as errorComponent};
2
+ //# sourceMappingURL=consent-B17hGrta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consent-DwuWkp63.js","names":["RouteErrorFallback","buildAuthorizeUrl","ConsentError","props","ErrorComponentProps","params","URLSearchParams","window","location","search","Object","fromEntries","entries","href","errorComponent"],"sources":["../../../frontend/src/routes/consent/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { ShieldCheckIcon, XIcon } from '@phosphor-icons/react';\nimport { useMutation, useSuspenseQuery } from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n} from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport {\n consentDecisionMutationOptions,\n getConsentInfoQueryOptions,\n} from '#frontend/queries/consent.ts';\n\nconst ConsentSearchSchema = OAuthSearchSchema.extend({\n client_id: z.string(),\n redirect_uri: z.string(),\n response_type: z.string(),\n});\n\nexport const Route = createFileRoute('/consent/')({\n component: Consent,\n errorComponent: ConsentError,\n validateSearch: ConsentSearchSchema,\n loaderDeps: ({ search }) => ({\n client_id: search.client_id,\n scope: search.scope,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(\n getConsentInfoQueryOptions({\n client_id: deps.client_id,\n scope: deps.scope,\n }),\n );\n },\n});\n\nfunction ConsentError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n // Re-enter the OAuth flow from the beginning so the\n // user can log in and be redirected back through\n // /oauth/authorize -> /consent/.\n const params = new URLSearchParams(window.location.search);\n const search = Object.fromEntries(params.entries());\n window.location.href = buildAuthorizeUrl(search);\n }}\n />\n );\n}\n\nfunction Consent() {\n const { t } = useTranslation();\n const search = Route.useSearch();\n\n const consentInfoQuery = useSuspenseQuery(\n getConsentInfoQueryOptions({\n client_id: search.client_id,\n scope: search.scope,\n }),\n );\n\n const consentMutation = useMutation({\n ...consentDecisionMutationOptions,\n onSuccess: (data) => {\n window.location.href = data.redirect_url;\n },\n });\n\n const handleAllow = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'allow',\n });\n };\n\n const handleDeny = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'deny',\n });\n };\n\n const { client, scopes, user } = consentInfoQuery.data;\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('consent.subtitle', { app: client.name })}\n title={t('consent.title')}\n />\n\n {/* User info */}\n <div className=\"mb-4 rounded-lg bg-base-200 p-3 text-center\">\n <p className=\"text-base-content/60 text-xs\">\n {t('consent.loggedInAs')}\n </p>\n <p className=\"font-medium text-sm\" data-testid=\"consent-user-email\">\n {user.email}\n </p>\n </div>\n\n {/* Requested permissions */}\n <div className=\"mb-4\">\n <h2 className=\"mb-3 font-semibold text-sm\">\n {t('consent.permissions.title')}\n </h2>\n <ul className=\"flex flex-col gap-2\" data-testid=\"consent-scope-list\">\n {scopes.map((scope: { name: string; description: string }) => (\n <li\n className=\"flex items-start gap-3 rounded-lg bg-base-200 p-3\"\n key={scope.name}\n >\n <div className=\"mt-0.5 rounded-full bg-primary/20 p-1\">\n <ShieldCheckIcon\n className=\"size-4 text-primary\"\n weight=\"fill\"\n />\n </div>\n <p className=\"font-medium text-sm\">\n {t(`consent.scope.${scope.name}`, {\n defaultValue: scope.description,\n })}\n </p>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n className=\"btn btn-outline h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-deny\"\n disabled={consentMutation.isPending}\n onClick={handleDeny}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <XIcon className=\"size-4\" weight=\"bold\" />\n {t('consent.deny')}\n </>\n )}\n </button>\n <button\n className=\"btn btn-primary h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-allow\"\n disabled={consentMutation.isPending}\n onClick={handleAllow}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <ShieldCheckIcon className=\"size-4\" weight=\"fill\" />\n {t('consent.allow')}\n </>\n )}\n </button>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"uGA4CA,SAASE,EAAaC,EAA4B,CAChD,OACE,EAAA,EAAA,KAAC,EAAD,CACE,GAAIA,EACJ,mBAAsB,CAIpB,IAAME,EAAS,IAAIC,gBAAgBC,OAAOC,SAASC,MAAM,EACnDA,EAASC,OAAOC,YAAYN,EAAOO,QAAQ,CAAC,EAClDL,OAAOC,SAASK,KAAOZ,EAAkBQ,CAAM,CACjD,CAAE,CAAA,CAGR"}
1
+ {"version":3,"file":"consent-B17hGrta.js","names":["RouteErrorFallback","buildAuthorizeUrl","ConsentError","props","ErrorComponentProps","params","URLSearchParams","window","location","search","Object","fromEntries","entries","href","errorComponent"],"sources":["../../../frontend/src/routes/consent/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { ShieldCheckIcon, XIcon } from '@phosphor-icons/react';\nimport { useMutation, useSuspenseQuery } from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n} from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport {\n consentDecisionMutationOptions,\n getConsentInfoQueryOptions,\n} from '#frontend/queries/consent.ts';\n\nconst ConsentSearchSchema = OAuthSearchSchema.extend({\n client_id: z.string(),\n redirect_uri: z.string(),\n response_type: z.string(),\n});\n\nexport const Route = createFileRoute('/consent/')({\n component: Consent,\n errorComponent: ConsentError,\n validateSearch: ConsentSearchSchema,\n loaderDeps: ({ search }) => ({\n client_id: search.client_id,\n scope: search.scope,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(\n getConsentInfoQueryOptions({\n client_id: deps.client_id,\n scope: deps.scope,\n }),\n );\n },\n});\n\nfunction ConsentError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n // Re-enter the OAuth flow from the beginning so the\n // user can log in and be redirected back through\n // /oauth/authorize -> /consent/.\n const params = new URLSearchParams(window.location.search);\n const search = Object.fromEntries(params.entries());\n window.location.href = buildAuthorizeUrl(search);\n }}\n />\n );\n}\n\nfunction Consent() {\n const { t } = useTranslation();\n const search = Route.useSearch();\n\n const consentInfoQuery = useSuspenseQuery(\n getConsentInfoQueryOptions({\n client_id: search.client_id,\n scope: search.scope,\n }),\n );\n\n const consentMutation = useMutation({\n ...consentDecisionMutationOptions,\n onSuccess: (data) => {\n window.location.href = data.redirect_url;\n },\n });\n\n const handleAllow = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'allow',\n });\n };\n\n const handleDeny = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'deny',\n });\n };\n\n const { client, scopes, user } = consentInfoQuery.data;\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('consent.subtitle', { app: client.name })}\n title={t('consent.title')}\n />\n\n {/* User info */}\n <div className=\"mb-4 rounded-lg bg-base-200 p-3 text-center\">\n <p className=\"text-base-content/60 text-xs\">\n {t('consent.loggedInAs')}\n </p>\n <p className=\"font-medium text-sm\" data-testid=\"consent-user-email\">\n {user.email}\n </p>\n </div>\n\n {/* Requested permissions */}\n <div className=\"mb-4\">\n <h2 className=\"mb-3 font-semibold text-sm\">\n {t('consent.permissions.title')}\n </h2>\n <ul className=\"flex flex-col gap-2\" data-testid=\"consent-scope-list\">\n {scopes.map((scope: { name: string; description: string }) => (\n <li\n className=\"flex items-start gap-3 rounded-lg bg-base-200 p-3\"\n key={scope.name}\n >\n <div className=\"mt-0.5 rounded-full bg-primary/20 p-1\">\n <ShieldCheckIcon\n className=\"size-4 text-primary\"\n weight=\"fill\"\n />\n </div>\n <p className=\"font-medium text-sm\">\n {t(`consent.scope.${scope.name}`, {\n defaultValue: scope.description,\n })}\n </p>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n className=\"btn btn-outline h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-deny\"\n disabled={consentMutation.isPending}\n onClick={handleDeny}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <XIcon className=\"size-4\" weight=\"bold\" />\n {t('consent.deny')}\n </>\n )}\n </button>\n <button\n className=\"btn btn-primary h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-allow\"\n disabled={consentMutation.isPending}\n onClick={handleAllow}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <ShieldCheckIcon className=\"size-4\" weight=\"fill\" />\n {t('consent.allow')}\n </>\n )}\n </button>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"uGA4CA,SAASE,EAAaC,EAA4B,CAChD,OACE,EAAA,EAAA,KAAC,EAAD,CACE,GAAIA,EACJ,mBAAsB,CAIpB,IAAME,EAAS,IAAIC,gBAAgBC,OAAOC,SAASC,MAAM,EACnDA,EAASC,OAAOC,YAAYN,EAAOO,QAAQ,CAAC,EAClDL,OAAOC,SAASK,KAAOZ,EAAkBQ,CAAM,CACjD,CAAE,CAAA,CAGR"}
@@ -1,2 +1,2 @@
1
- import{o as e,r as t}from"./IconBase.es-d5KP98Ac.js";import{Q as n}from"./use-theme-cVUDAjtt.js";import{t as r}from"./useMutation-DVMopbtG.js";import{t as i}from"./page-layout-C475gs09.js";import{t as a}from"./ShieldCheck.es-CscPsYbC.js";import{t as o}from"./X.es-IwdB4hWT.js";import{_ as s,v as c,y as l}from"./index-CsT6OVnP.js";import{t as u}from"./page-header-BYMFSGfT.js";var d=e();function f(){let{t:e}=t(),f=s.useSearch(),p=n(l({client_id:f.client_id,scope:f.scope})),m=r({...c,onSuccess:e=>{window.location.href=e.redirect_url}}),h=()=>{m.mutate({client_id:f.client_id,redirect_uri:f.redirect_uri,response_type:f.response_type,scope:f.scope,state:f.state,nonce:f.nonce,code_challenge:f.code_challenge,code_challenge_method:f.code_challenge_method,prompt:f.prompt,max_age:f.max_age,display:f.display,response_mode:f.response_mode,login_hint:f.login_hint,ui_locales:f.ui_locales,id_token_hint:f.id_token_hint,acr_values:f.acr_values,account_selected:f.account_selected,account_selection_state:f.account_selection_state,decision:`allow`})},g=()=>{m.mutate({client_id:f.client_id,redirect_uri:f.redirect_uri,response_type:f.response_type,scope:f.scope,state:f.state,nonce:f.nonce,code_challenge:f.code_challenge,code_challenge_method:f.code_challenge_method,prompt:f.prompt,max_age:f.max_age,display:f.display,response_mode:f.response_mode,login_hint:f.login_hint,ui_locales:f.ui_locales,id_token_hint:f.id_token_hint,acr_values:f.acr_values,account_selected:f.account_selected,account_selection_state:f.account_selection_state,decision:`deny`})},{client:_,scopes:v,user:y}=p.data;return(0,d.jsxs)(i,{cardPadding:!0,maxWidth:`100`,children:[(0,d.jsx)(u,{subtitle:e(`consent.subtitle`,{app:_.name}),title:e(`consent.title`)}),(0,d.jsxs)(`div`,{className:`mb-4 rounded-lg bg-base-200 p-3 text-center`,children:[(0,d.jsx)(`p`,{className:`text-base-content/60 text-xs`,children:e(`consent.loggedInAs`)}),(0,d.jsx)(`p`,{className:`font-medium text-sm`,"data-testid":`consent-user-email`,children:y.email})]}),(0,d.jsxs)(`div`,{className:`mb-4`,children:[(0,d.jsx)(`h2`,{className:`mb-3 font-semibold text-sm`,children:e(`consent.permissions.title`)}),(0,d.jsx)(`ul`,{className:`flex flex-col gap-2`,"data-testid":`consent-scope-list`,children:v.map(t=>(0,d.jsxs)(`li`,{className:`flex items-start gap-3 rounded-lg bg-base-200 p-3`,children:[(0,d.jsx)(`div`,{className:`mt-0.5 rounded-full bg-primary/20 p-1`,children:(0,d.jsx)(a,{className:`size-4 text-primary`,weight:`fill`})}),(0,d.jsx)(`p`,{className:`font-medium text-sm`,children:e(`consent.scope.${t.name}`,{defaultValue:t.description})})]},t.name))})]}),(0,d.jsxs)(`div`,{className:`flex gap-3`,children:[(0,d.jsx)(`button`,{className:`btn btn-outline h-10 flex-1 font-semibold text-[14px]`,"data-testid":`consent-deny`,disabled:m.isPending,onClick:g,type:`button`,children:m.isPending?(0,d.jsx)(`span`,{className:`loading loading-spinner loading-sm`}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(o,{className:`size-4`,weight:`bold`}),e(`consent.deny`)]})}),(0,d.jsx)(`button`,{className:`btn btn-primary h-10 flex-1 font-semibold text-[14px]`,"data-testid":`consent-allow`,disabled:m.isPending,onClick:h,type:`button`,children:m.isPending?(0,d.jsx)(`span`,{className:`loading loading-spinner loading-sm`}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(a,{className:`size-4`,weight:`fill`}),e(`consent.allow`)]})})]})]})}export{f as component};
2
- //# sourceMappingURL=consent-C5Qo0iLd.js.map
1
+ import{o as e,r as t}from"./IconBase.es-d5KP98Ac.js";import{Q as n}from"./use-theme-cVUDAjtt.js";import{t as r}from"./useMutation-Iu4AJCB4.js";import{t as i}from"./page-layout-C475gs09.js";import{t as a}from"./ShieldCheck.es-CscPsYbC.js";import{t as o}from"./X.es-IwdB4hWT.js";import{_ as s,v as c,y as l}from"./index-BmfaaNx6.js";import{t as u}from"./page-header-BYMFSGfT.js";var d=e();function f(){let{t:e}=t(),f=s.useSearch(),p=n(l({client_id:f.client_id,scope:f.scope})),m=r({...c,onSuccess:e=>{window.location.href=e.redirect_url}}),h=()=>{m.mutate({client_id:f.client_id,redirect_uri:f.redirect_uri,response_type:f.response_type,scope:f.scope,state:f.state,nonce:f.nonce,code_challenge:f.code_challenge,code_challenge_method:f.code_challenge_method,prompt:f.prompt,max_age:f.max_age,display:f.display,response_mode:f.response_mode,login_hint:f.login_hint,ui_locales:f.ui_locales,id_token_hint:f.id_token_hint,acr_values:f.acr_values,account_selected:f.account_selected,account_selection_state:f.account_selection_state,decision:`allow`})},g=()=>{m.mutate({client_id:f.client_id,redirect_uri:f.redirect_uri,response_type:f.response_type,scope:f.scope,state:f.state,nonce:f.nonce,code_challenge:f.code_challenge,code_challenge_method:f.code_challenge_method,prompt:f.prompt,max_age:f.max_age,display:f.display,response_mode:f.response_mode,login_hint:f.login_hint,ui_locales:f.ui_locales,id_token_hint:f.id_token_hint,acr_values:f.acr_values,account_selected:f.account_selected,account_selection_state:f.account_selection_state,decision:`deny`})},{client:_,scopes:v,user:y}=p.data;return(0,d.jsxs)(i,{cardPadding:!0,maxWidth:`100`,children:[(0,d.jsx)(u,{subtitle:e(`consent.subtitle`,{app:_.name}),title:e(`consent.title`)}),(0,d.jsxs)(`div`,{className:`mb-4 rounded-lg bg-base-200 p-3 text-center`,children:[(0,d.jsx)(`p`,{className:`text-base-content/60 text-xs`,children:e(`consent.loggedInAs`)}),(0,d.jsx)(`p`,{className:`font-medium text-sm`,"data-testid":`consent-user-email`,children:y.email})]}),(0,d.jsxs)(`div`,{className:`mb-4`,children:[(0,d.jsx)(`h2`,{className:`mb-3 font-semibold text-sm`,children:e(`consent.permissions.title`)}),(0,d.jsx)(`ul`,{className:`flex flex-col gap-2`,"data-testid":`consent-scope-list`,children:v.map(t=>(0,d.jsxs)(`li`,{className:`flex items-start gap-3 rounded-lg bg-base-200 p-3`,children:[(0,d.jsx)(`div`,{className:`mt-0.5 rounded-full bg-primary/20 p-1`,children:(0,d.jsx)(a,{className:`size-4 text-primary`,weight:`fill`})}),(0,d.jsx)(`p`,{className:`font-medium text-sm`,children:e(`consent.scope.${t.name}`,{defaultValue:t.description})})]},t.name))})]}),(0,d.jsxs)(`div`,{className:`flex gap-3`,children:[(0,d.jsx)(`button`,{className:`btn btn-outline h-10 flex-1 font-semibold text-[14px]`,"data-testid":`consent-deny`,disabled:m.isPending,onClick:g,type:`button`,children:m.isPending?(0,d.jsx)(`span`,{className:`loading loading-spinner loading-sm`}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(o,{className:`size-4`,weight:`bold`}),e(`consent.deny`)]})}),(0,d.jsx)(`button`,{className:`btn btn-primary h-10 flex-1 font-semibold text-[14px]`,"data-testid":`consent-allow`,disabled:m.isPending,onClick:h,type:`button`,children:m.isPending?(0,d.jsx)(`span`,{className:`loading loading-spinner loading-sm`}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(a,{className:`size-4`,weight:`fill`}),e(`consent.allow`)]})})]})]})}export{f as component};
2
+ //# sourceMappingURL=consent-C-h_pWHQ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"consent-C5Qo0iLd.js","names":["ShieldCheckIcon","XIcon","useMutation","useSuspenseQuery","useTranslation","PageHeader","PageLayout","consentDecisionMutationOptions","getConsentInfoQueryOptions","Route","Consent","t","search","useSearch","consentInfoQuery","client_id","scope","consentMutation","onSuccess","data","window","location","href","redirect_url","handleAllow","mutate","redirect_uri","response_type","state","nonce","code_challenge","code_challenge_method","prompt","max_age","display","response_mode","login_hint","ui_locales","id_token_hint","acr_values","account_selected","account_selection_state","decision","handleDeny","client","scopes","user","app","name","email","map","description","defaultValue","isPending","component"],"sources":["../../../frontend/src/routes/consent/index.tsx?tsr-split=component"],"sourcesContent":["import { ShieldCheckIcon, XIcon } from '@phosphor-icons/react';\nimport { useMutation, useSuspenseQuery } from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n} from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport {\n consentDecisionMutationOptions,\n getConsentInfoQueryOptions,\n} from '#frontend/queries/consent.ts';\n\nconst ConsentSearchSchema = OAuthSearchSchema.extend({\n client_id: z.string(),\n redirect_uri: z.string(),\n response_type: z.string(),\n});\n\nexport const Route = createFileRoute('/consent/')({\n component: Consent,\n errorComponent: ConsentError,\n validateSearch: ConsentSearchSchema,\n loaderDeps: ({ search }) => ({\n client_id: search.client_id,\n scope: search.scope,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(\n getConsentInfoQueryOptions({\n client_id: deps.client_id,\n scope: deps.scope,\n }),\n );\n },\n});\n\nfunction ConsentError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n // Re-enter the OAuth flow from the beginning so the\n // user can log in and be redirected back through\n // /oauth/authorize -> /consent/.\n const params = new URLSearchParams(window.location.search);\n const search = Object.fromEntries(params.entries());\n window.location.href = buildAuthorizeUrl(search);\n }}\n />\n );\n}\n\nfunction Consent() {\n const { t } = useTranslation();\n const search = Route.useSearch();\n\n const consentInfoQuery = useSuspenseQuery(\n getConsentInfoQueryOptions({\n client_id: search.client_id,\n scope: search.scope,\n }),\n );\n\n const consentMutation = useMutation({\n ...consentDecisionMutationOptions,\n onSuccess: (data) => {\n window.location.href = data.redirect_url;\n },\n });\n\n const handleAllow = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'allow',\n });\n };\n\n const handleDeny = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'deny',\n });\n };\n\n const { client, scopes, user } = consentInfoQuery.data;\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('consent.subtitle', { app: client.name })}\n title={t('consent.title')}\n />\n\n {/* User info */}\n <div className=\"mb-4 rounded-lg bg-base-200 p-3 text-center\">\n <p className=\"text-base-content/60 text-xs\">\n {t('consent.loggedInAs')}\n </p>\n <p className=\"font-medium text-sm\" data-testid=\"consent-user-email\">\n {user.email}\n </p>\n </div>\n\n {/* Requested permissions */}\n <div className=\"mb-4\">\n <h2 className=\"mb-3 font-semibold text-sm\">\n {t('consent.permissions.title')}\n </h2>\n <ul className=\"flex flex-col gap-2\" data-testid=\"consent-scope-list\">\n {scopes.map((scope: { name: string; description: string }) => (\n <li\n className=\"flex items-start gap-3 rounded-lg bg-base-200 p-3\"\n key={scope.name}\n >\n <div className=\"mt-0.5 rounded-full bg-primary/20 p-1\">\n <ShieldCheckIcon\n className=\"size-4 text-primary\"\n weight=\"fill\"\n />\n </div>\n <p className=\"font-medium text-sm\">\n {t(`consent.scope.${scope.name}`, {\n defaultValue: scope.description,\n })}\n </p>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n className=\"btn btn-outline h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-deny\"\n disabled={consentMutation.isPending}\n onClick={handleDeny}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <XIcon className=\"size-4\" weight=\"bold\" />\n {t('consent.deny')}\n </>\n )}\n </button>\n <button\n className=\"btn btn-primary h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-allow\"\n disabled={consentMutation.isPending}\n onClick={handleAllow}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <ShieldCheckIcon className=\"size-4\" weight=\"fill\" />\n {t('consent.allow')}\n </>\n )}\n </button>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"consent-C-h_pWHQ.js","names":["ShieldCheckIcon","XIcon","useMutation","useSuspenseQuery","useTranslation","PageHeader","PageLayout","consentDecisionMutationOptions","getConsentInfoQueryOptions","Route","Consent","t","search","useSearch","consentInfoQuery","client_id","scope","consentMutation","onSuccess","data","window","location","href","redirect_url","handleAllow","mutate","redirect_uri","response_type","state","nonce","code_challenge","code_challenge_method","prompt","max_age","display","response_mode","login_hint","ui_locales","id_token_hint","acr_values","account_selected","account_selection_state","decision","handleDeny","client","scopes","user","app","name","email","map","description","defaultValue","isPending","component"],"sources":["../../../frontend/src/routes/consent/index.tsx?tsr-split=component"],"sourcesContent":["import { ShieldCheckIcon, XIcon } from '@phosphor-icons/react';\nimport { useMutation, useSuspenseQuery } from '@tanstack/react-query';\nimport {\n createFileRoute,\n type ErrorComponentProps,\n} from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport {\n consentDecisionMutationOptions,\n getConsentInfoQueryOptions,\n} from '#frontend/queries/consent.ts';\n\nconst ConsentSearchSchema = OAuthSearchSchema.extend({\n client_id: z.string(),\n redirect_uri: z.string(),\n response_type: z.string(),\n});\n\nexport const Route = createFileRoute('/consent/')({\n component: Consent,\n errorComponent: ConsentError,\n validateSearch: ConsentSearchSchema,\n loaderDeps: ({ search }) => ({\n client_id: search.client_id,\n scope: search.scope,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(\n getConsentInfoQueryOptions({\n client_id: deps.client_id,\n scope: deps.scope,\n }),\n );\n },\n});\n\nfunction ConsentError(props: ErrorComponentProps) {\n return (\n <RouteErrorFallback\n {...props}\n onUnauthorized={() => {\n // Re-enter the OAuth flow from the beginning so the\n // user can log in and be redirected back through\n // /oauth/authorize -> /consent/.\n const params = new URLSearchParams(window.location.search);\n const search = Object.fromEntries(params.entries());\n window.location.href = buildAuthorizeUrl(search);\n }}\n />\n );\n}\n\nfunction Consent() {\n const { t } = useTranslation();\n const search = Route.useSearch();\n\n const consentInfoQuery = useSuspenseQuery(\n getConsentInfoQueryOptions({\n client_id: search.client_id,\n scope: search.scope,\n }),\n );\n\n const consentMutation = useMutation({\n ...consentDecisionMutationOptions,\n onSuccess: (data) => {\n window.location.href = data.redirect_url;\n },\n });\n\n const handleAllow = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'allow',\n });\n };\n\n const handleDeny = () => {\n consentMutation.mutate({\n client_id: search.client_id,\n redirect_uri: search.redirect_uri,\n response_type: search.response_type,\n scope: search.scope,\n state: search.state,\n nonce: search.nonce,\n code_challenge: search.code_challenge,\n code_challenge_method: search.code_challenge_method,\n prompt: search.prompt,\n max_age: search.max_age,\n display: search.display,\n response_mode: search.response_mode,\n login_hint: search.login_hint,\n ui_locales: search.ui_locales,\n id_token_hint: search.id_token_hint,\n acr_values: search.acr_values,\n account_selected: search.account_selected,\n account_selection_state: search.account_selection_state,\n decision: 'deny',\n });\n };\n\n const { client, scopes, user } = consentInfoQuery.data;\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('consent.subtitle', { app: client.name })}\n title={t('consent.title')}\n />\n\n {/* User info */}\n <div className=\"mb-4 rounded-lg bg-base-200 p-3 text-center\">\n <p className=\"text-base-content/60 text-xs\">\n {t('consent.loggedInAs')}\n </p>\n <p className=\"font-medium text-sm\" data-testid=\"consent-user-email\">\n {user.email}\n </p>\n </div>\n\n {/* Requested permissions */}\n <div className=\"mb-4\">\n <h2 className=\"mb-3 font-semibold text-sm\">\n {t('consent.permissions.title')}\n </h2>\n <ul className=\"flex flex-col gap-2\" data-testid=\"consent-scope-list\">\n {scopes.map((scope: { name: string; description: string }) => (\n <li\n className=\"flex items-start gap-3 rounded-lg bg-base-200 p-3\"\n key={scope.name}\n >\n <div className=\"mt-0.5 rounded-full bg-primary/20 p-1\">\n <ShieldCheckIcon\n className=\"size-4 text-primary\"\n weight=\"fill\"\n />\n </div>\n <p className=\"font-medium text-sm\">\n {t(`consent.scope.${scope.name}`, {\n defaultValue: scope.description,\n })}\n </p>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n className=\"btn btn-outline h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-deny\"\n disabled={consentMutation.isPending}\n onClick={handleDeny}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <XIcon className=\"size-4\" weight=\"bold\" />\n {t('consent.deny')}\n </>\n )}\n </button>\n <button\n className=\"btn btn-primary h-10 flex-1 font-semibold text-[14px]\"\n data-testid=\"consent-allow\"\n disabled={consentMutation.isPending}\n onClick={handleAllow}\n type=\"button\"\n >\n {consentMutation.isPending ? (\n <span className=\"loading loading-spinner loading-sm\" />\n ) : (\n <>\n <ShieldCheckIcon className=\"size-4\" weight=\"fill\" />\n {t('consent.allow')}\n </>\n )}\n </button>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"mYA4DA,SAASU,GAAU,CACjB,GAAM,CAAEC,KAAMP,EAAe,EACvBQ,EAASH,EAAMI,UAAU,EAEzBC,EAAmBX,EACvBK,EAA2B,CACzBO,UAAWH,EAAOG,UAClBC,MAAOJ,EAAOI,KAChB,CAAC,CACH,EAEMC,EAAkBf,EAAY,CAClC,GAAGK,EACHW,UAAYC,GAAS,CACnBC,OAAOC,SAASC,KAAOH,EAAKI,YAC9B,CACF,CAAC,EAEKC,MAAoB,CACxBP,EAAgBQ,OAAO,CACrBV,UAAWH,EAAOG,UAClBW,aAAcd,EAAOc,aACrBC,cAAef,EAAOe,cACtBX,MAAOJ,EAAOI,MACdY,MAAOhB,EAAOgB,MACdC,MAAOjB,EAAOiB,MACdC,eAAgBlB,EAAOkB,eACvBC,sBAAuBnB,EAAOmB,sBAC9BC,OAAQpB,EAAOoB,OACfC,QAASrB,EAAOqB,QAChBC,QAAStB,EAAOsB,QAChBC,cAAevB,EAAOuB,cACtBC,WAAYxB,EAAOwB,WACnBC,WAAYzB,EAAOyB,WACnBC,cAAe1B,EAAO0B,cACtBC,WAAY3B,EAAO2B,WACnBC,iBAAkB5B,EAAO4B,iBACzBC,wBAAyB7B,EAAO6B,wBAChCC,SAAU,OACZ,CAAC,CACH,EAEMC,MAAmB,CACvB1B,EAAgBQ,OAAO,CACrBV,UAAWH,EAAOG,UAClBW,aAAcd,EAAOc,aACrBC,cAAef,EAAOe,cACtBX,MAAOJ,EAAOI,MACdY,MAAOhB,EAAOgB,MACdC,MAAOjB,EAAOiB,MACdC,eAAgBlB,EAAOkB,eACvBC,sBAAuBnB,EAAOmB,sBAC9BC,OAAQpB,EAAOoB,OACfC,QAASrB,EAAOqB,QAChBC,QAAStB,EAAOsB,QAChBC,cAAevB,EAAOuB,cACtBC,WAAYxB,EAAOwB,WACnBC,WAAYzB,EAAOyB,WACnBC,cAAe1B,EAAO0B,cACtBC,WAAY3B,EAAO2B,WACnBC,iBAAkB5B,EAAO4B,iBACzBC,wBAAyB7B,EAAO6B,wBAChCC,SAAU,MACZ,CAAC,CACH,EAEM,CAAEE,SAAQC,SAAQC,QAAShC,EAAiBK,KAElD,OACE,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CACE,SAAUR,EAAE,mBAAoB,CAAEoC,IAAKH,EAAOI,IAAK,CAAC,EACpD,MAAOrC,EAAE,eAAe,CAAE,CAAA,GAI5B,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,uDAAf,EACE,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,wCACVA,EAAE,oBAAoB,CACtB,CAAA,GACH,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,sBAAsB,cAAY,8BAC5CmC,EAAKG,KACL,CAAA,CACA,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,gBAAf,EACE,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,sCACXtC,EAAE,2BAA2B,CAC5B,CAAA,GACJ,EAAA,EAAA,KAAC,KAAD,CAAI,UAAU,sBAAsB,cAAY,8BAC7CkC,EAAOK,IAAKlC,IACX,EAAA,EAAA,MAAC,KAAD,CACE,UAAU,6DADZ,EAIE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,kDACb,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,sBACV,OAAO,MAAM,CAAA,CAEZ,CAAA,GACL,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,+BACVL,EAAE,iBAAiBK,EAAMgC,OAAQ,CAChCI,aAAcpC,EAAMmC,WACtB,CAAC,CACA,CAAA,CACD,GAbGnC,EAAMgC,IAaT,CACL,CACC,CAAA,CACD,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,sBAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,wDACV,cAAY,eACZ,SAAU/B,EAAgBoC,UAC1B,QAASV,EACT,KAAK,kBAEJ1B,EAAgBoC,WACf,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,oCAAoC,CAAA,GAEpD,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,SAAS,OAAO,MAAM,CAAA,EACtC1C,EAAE,cAAc,CACnB,CAAA,CAAA,CAEI,CAAA,GACR,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,wDACV,cAAY,gBACZ,SAAUM,EAAgBoC,UAC1B,QAAS7B,EACT,KAAK,kBAEJP,EAAgBoC,WACf,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,oCAAoC,CAAA,GAEpD,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAD,CAAiB,UAAU,SAAS,OAAO,MAAM,CAAA,EAChD1C,EAAE,eAAe,CACpB,CAAA,CAAA,CAEI,CAAA,CACL,GACK,GAEhB"}
@@ -0,0 +1,2 @@
1
+ import{N as e}from"./index-BmfaaNx6.js";var t=e;export{t as errorComponent};
2
+ //# sourceMappingURL=email-BMEIiDXv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-SSKfXB7c.js","names":["RouteErrorFallback","VerifyEmailFormValues","token","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/verify/email/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n CheckCircleIcon,\n EnvelopeSimpleIcon,\n KeyIcon,\n} from '@phosphor-icons/react';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { Divider } from '#frontend/components/ui/divider.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} 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 resendVerificationMutationOptions,\n verifyEmailMutationOptions,\n} from '#frontend/queries/verify-email.ts';\n\nconst SearchSchema = z.object({\n ...OAuthSearchSchema.shape,\n token: z.string().default(''),\n email: z.string().default(''),\n});\n\nexport const Route = createFileRoute('/verify/email/')({\n component: VerifyEmail,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype VerifyEmailFormValues = {\n token: string;\n};\n\nfunction VerifyEmail() {\n const { t } = useTranslation();\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { token: queryToken, email } = search;\n const [verified, setVerified] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const verifyEmailSchema = useMemo(\n () =>\n z.object({\n token: z.string().min(1, t('validation.token.required')),\n }),\n [t],\n );\n\n const verifyEmailMutation = useMutation({\n ...verifyEmailMutationOptions,\n onSuccess: async (data) => {\n const user = data.user;\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n if (user.second_factor_required) {\n const available_2fa_methods: SecondFactorMethod[] = [];\n if (appConfig.auth.password.totp.enabled) {\n available_2fa_methods.push('totp');\n }\n if (appConfig.auth.passkey.enabled) {\n available_2fa_methods.push('passkey');\n }\n\n if (available_2fa_methods.length === 1) {\n const method = available_2fa_methods[0];\n if (method === 'totp') {\n return navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return navigate({\n to: '/setup/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n return navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n }\n }\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthorizeUrl(search);\n } else {\n setVerified(true);\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const resendVerificationMutation = useMutation({\n ...resendVerificationMutationOptions,\n onSuccess: () => {\n setResendSuccess(true);\n setTimeout(() => setResendSuccess(false), 5000);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<VerifyEmailFormValues>({\n defaultValues: {\n token: queryToken || '',\n },\n resolver: standardSchemaResolver(verifyEmailSchema),\n });\n\n const onSubmit = async (values: VerifyEmailFormValues) => {\n try {\n await verifyEmailMutation.mutateAsync(values);\n } catch (_error) {\n setError('token', {\n type: 'manual',\n message: t('verifyEmail.error.invalidToken'),\n });\n }\n };\n\n const handleResend = async () => {\n if (!email) {\n return;\n }\n try {\n await resendVerificationMutation.mutateAsync({ email });\n } catch (_error) {}\n };\n\n if (verified) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('verifyEmail.success.description')}\n title={t('verifyEmail.success.subtitle')}\n />\n\n <button\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n data-testid=\"email-verify-go-profile\"\n onClick={() => navigate({ to: '/profile' })}\n type=\"button\"\n >\n {t('verifyEmail.success.goToProfile')}\n </button>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('verifyEmail.subtitle')}\n title={t('verifyEmail.title')}\n />\n\n {email && (\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n <div className=\"text-left\">\n <p className=\"font-semibold\">{t('register.success.subtitle')}</p>\n <p className=\"text-xs\">\n {t('register.success.description', { email })}\n </p>\n </div>\n </Alert>\n )}\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n error={errors.token}\n icon={KeyIcon}\n placeholder={t('verifyEmail.token.placeholder')}\n {...register('token')}\n type=\"text\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={verifyEmailMutation.isPending}\n pendingText={t('verifyEmail.submitting')}\n >\n {t('verifyEmail.submit')}\n </SubmitButton>\n </form>\n\n {email && (\n <>\n <Divider />\n\n {resendSuccess && (\n <Alert className=\"mb-2\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.resendSuccess')}\n </Alert>\n )}\n\n <button\n className=\"btn btn-ghost btn-sm w-full\"\n data-testid=\"email-verify-resend\"\n disabled={resendVerificationMutation.isPending || resendSuccess}\n onClick={handleResend}\n type=\"button\"\n >\n {resendVerificationMutation.isPending ? (\n <>\n <span className=\"loading loading-spinner loading-xs\" />\n {t('verifyEmail.resending')}\n </>\n ) : (\n t('verifyEmail.resend')\n )}\n </button>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"wCAgEE,IAAAG,EA3COH"}
1
+ {"version":3,"file":"email-BMEIiDXv.js","names":["RouteErrorFallback","VerifyEmailFormValues","token","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/verify/email/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n CheckCircleIcon,\n EnvelopeSimpleIcon,\n KeyIcon,\n} from '@phosphor-icons/react';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { Divider } from '#frontend/components/ui/divider.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} 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 resendVerificationMutationOptions,\n verifyEmailMutationOptions,\n} from '#frontend/queries/verify-email.ts';\n\nconst SearchSchema = z.object({\n ...OAuthSearchSchema.shape,\n token: z.string().default(''),\n email: z.string().default(''),\n});\n\nexport const Route = createFileRoute('/verify/email/')({\n component: VerifyEmail,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype VerifyEmailFormValues = {\n token: string;\n};\n\nfunction VerifyEmail() {\n const { t } = useTranslation();\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { token: queryToken, email } = search;\n const [verified, setVerified] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const verifyEmailSchema = useMemo(\n () =>\n z.object({\n token: z.string().min(1, t('validation.token.required')),\n }),\n [t],\n );\n\n const verifyEmailMutation = useMutation({\n ...verifyEmailMutationOptions,\n onSuccess: async (data) => {\n const user = data.user;\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n if (user.second_factor_required) {\n const available_2fa_methods: SecondFactorMethod[] = [];\n if (appConfig.auth.password.totp.enabled) {\n available_2fa_methods.push('totp');\n }\n if (appConfig.auth.passkey.enabled) {\n available_2fa_methods.push('passkey');\n }\n\n if (available_2fa_methods.length === 1) {\n const method = available_2fa_methods[0];\n if (method === 'totp') {\n return navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return navigate({\n to: '/setup/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n return navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n }\n }\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthorizeUrl(search);\n } else {\n setVerified(true);\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const resendVerificationMutation = useMutation({\n ...resendVerificationMutationOptions,\n onSuccess: () => {\n setResendSuccess(true);\n setTimeout(() => setResendSuccess(false), 5000);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<VerifyEmailFormValues>({\n defaultValues: {\n token: queryToken || '',\n },\n resolver: standardSchemaResolver(verifyEmailSchema),\n });\n\n const onSubmit = async (values: VerifyEmailFormValues) => {\n try {\n await verifyEmailMutation.mutateAsync(values);\n } catch (_error) {\n setError('token', {\n type: 'manual',\n message: t('verifyEmail.error.invalidToken'),\n });\n }\n };\n\n const handleResend = async () => {\n if (!email) {\n return;\n }\n try {\n await resendVerificationMutation.mutateAsync({ email });\n } catch (_error) {}\n };\n\n if (verified) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('verifyEmail.success.description')}\n title={t('verifyEmail.success.subtitle')}\n />\n\n <button\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n data-testid=\"email-verify-go-profile\"\n onClick={() => navigate({ to: '/profile' })}\n type=\"button\"\n >\n {t('verifyEmail.success.goToProfile')}\n </button>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('verifyEmail.subtitle')}\n title={t('verifyEmail.title')}\n />\n\n {email && (\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n <div className=\"text-left\">\n <p className=\"font-semibold\">{t('register.success.subtitle')}</p>\n <p className=\"text-xs\">\n {t('register.success.description', { email })}\n </p>\n </div>\n </Alert>\n )}\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n error={errors.token}\n icon={KeyIcon}\n placeholder={t('verifyEmail.token.placeholder')}\n {...register('token')}\n type=\"text\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={verifyEmailMutation.isPending}\n pendingText={t('verifyEmail.submitting')}\n >\n {t('verifyEmail.submit')}\n </SubmitButton>\n </form>\n\n {email && (\n <>\n <Divider />\n\n {resendSuccess && (\n <Alert className=\"mb-2\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.resendSuccess')}\n </Alert>\n )}\n\n <button\n className=\"btn btn-ghost btn-sm w-full\"\n data-testid=\"email-verify-resend\"\n disabled={resendVerificationMutation.isPending || resendSuccess}\n onClick={handleResend}\n type=\"button\"\n >\n {resendVerificationMutation.isPending ? (\n <>\n <span className=\"loading loading-spinner loading-xs\" />\n {t('verifyEmail.resending')}\n </>\n ) : (\n t('verifyEmail.resend')\n )}\n </button>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"wCAgEE,IAAAG,EA3COH"}
@@ -1,2 +1,2 @@
1
- import{o as e,r as t,s as n,u as r}from"./IconBase.es-d5KP98Ac.js";import{Q as i,a,i as o,n as s,ut as c}from"./use-theme-cVUDAjtt.js";import{t as l}from"./useMutation-DVMopbtG.js";import{t as u}from"./mutationOptions-Dfvzj6n2.js";import{t as d}from"./CheckCircle.es-MnJIACCe.js";import{t as f}from"./page-layout-C475gs09.js";import{t as p}from"./EnvelopeSimple.es-BZ7u3LYh.js";import{t as m}from"./Key.es-l5aSxw0I.js";import{c as h,o as g}from"./zod-BItJDQBQ.js";import{A as _,F as v,M as y,S as b,j as x,p as S}from"./index-CsT6OVnP.js";import{t as C}from"./page-header-BYMFSGfT.js";import{t as w}from"./alert-CSXqgDVi.js";import{t as T}from"./promise-OpBtq8tG.js";import{r as E,t as D}from"./standard-schema-o4V-s4uY.js";import{t as O}from"./icon-input-8iU7PNzd.js";import{t as k}from"./submit-button-Xx6DwLyh.js";var A=r(n()),j=e();function M({text:e,className:t=``}){return(0,j.jsxs)(`div`,{className:`my-4 flex items-center ${t}`,children:[(0,j.jsx)(`div`,{className:`h-px flex-1 bg-base-200`}),e&&(0,j.jsx)(`span`,{className:`px-3 text-base-content/60 text-sm`,children:e}),(0,j.jsx)(`div`,{className:`h-px flex-1 bg-base-200`})]})}var N=u({mutationFn:async e=>a(await o.api.auth.email.verify.$post({json:e}))}),P=u({mutationFn:async e=>a(await o.api.auth.email.resend.$post({json:e,header:{}}))});function F(){let{t:e}=t(),n=v(),r=c(),a=S.useSearch(),{token:o,email:u}=a,[F,I]=(0,A.useState)(!1),[L,R]=(0,A.useState)(!1),{data:z}=i(s),B=(0,A.useMemo)(()=>g({token:h().min(1,e(`validation.token.required`))}),[e]),V=l({...N,onSuccess:async e=>{let t=e.user;if(r.setQueryData(b.queryKey,{user:t}),await T(),t.second_factor_required){let e=[];return z.auth.password.totp.enabled&&e.push(`totp`),z.auth.passkey.enabled&&e.push(`passkey`),e.length===1?e[0]===`totp`?n({to:`/setup/totp`,search:x(a)}):n({to:`/setup/passkey`,search:x(a)}):n({to:`/setup/2fa`,search:x(a)})}y(a)?window.location.href=_(a):I(!0)},onSettled:()=>{r.invalidateQueries({queryKey:b.queryKey})}}),H=l({...P,onSuccess:()=>{R(!0),setTimeout(()=>R(!1),5e3)}}),{register:U,setError:W,handleSubmit:G,formState:{errors:K}}=E({defaultValues:{token:o||``},resolver:D(B)}),q=async t=>{try{await V.mutateAsync(t)}catch{W(`token`,{type:`manual`,message:e(`verifyEmail.error.invalidToken`)})}},J=async()=>{if(u)try{await H.mutateAsync({email:u})}catch{}};return F?(0,j.jsxs)(f,{cardPadding:!0,maxWidth:`100`,children:[(0,j.jsx)(w,{className:`mb-4`,icon:d,type:`success`,children:e(`verifyEmail.success.title`)}),(0,j.jsx)(C,{subtitle:e(`verifyEmail.success.description`),title:e(`verifyEmail.success.subtitle`)}),(0,j.jsx)(`button`,{className:`btn btn-block h-10 font-semibold text-[14px]`,"data-testid":`email-verify-go-profile`,onClick:()=>n({to:`/profile`}),type:`button`,children:e(`verifyEmail.success.goToProfile`)})]}):(0,j.jsxs)(f,{cardPadding:!0,maxWidth:`100`,children:[(0,j.jsx)(C,{subtitle:e(`verifyEmail.subtitle`),title:e(`verifyEmail.title`)}),u&&(0,j.jsx)(w,{className:`mb-4`,icon:p,type:`info`,children:(0,j.jsxs)(`div`,{className:`text-left`,children:[(0,j.jsx)(`p`,{className:`font-semibold`,children:e(`register.success.subtitle`)}),(0,j.jsx)(`p`,{className:`text-xs`,children:e(`register.success.description`,{email:u})})]})}),(0,j.jsxs)(`form`,{className:`flex flex-col gap-4`,onSubmit:G(q),children:[(0,j.jsx)(O,{error:K.token,icon:m,placeholder:e(`verifyEmail.token.placeholder`),...U(`token`),type:`text`}),(0,j.jsx)(k,{className:`mt-2`,isPending:V.isPending,pendingText:e(`verifyEmail.submitting`),children:e(`verifyEmail.submit`)})]}),u&&(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(M,{}),L&&(0,j.jsx)(w,{className:`mb-2`,icon:d,type:`success`,children:e(`verifyEmail.resendSuccess`)}),(0,j.jsx)(`button`,{className:`btn btn-ghost btn-sm w-full`,"data-testid":`email-verify-resend`,disabled:H.isPending||L,onClick:J,type:`button`,children:H.isPending?(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(`span`,{className:`loading loading-spinner loading-xs`}),e(`verifyEmail.resending`)]}):e(`verifyEmail.resend`)})]})]})}export{F as component};
2
- //# sourceMappingURL=email-CIttZRBe.js.map
1
+ import{o as e,r as t,s as n,u as r}from"./IconBase.es-d5KP98Ac.js";import{Q as i,a,i as o,n as s,ut as c}from"./use-theme-cVUDAjtt.js";import{t as l}from"./useMutation-Iu4AJCB4.js";import{t as u}from"./mutationOptions-Dfvzj6n2.js";import{t as d}from"./CheckCircle.es-MnJIACCe.js";import{t as f}from"./page-layout-C475gs09.js";import{t as p}from"./EnvelopeSimple.es-BZ7u3LYh.js";import{t as m}from"./Key.es-l5aSxw0I.js";import{c as h,o as g}from"./zod-BItJDQBQ.js";import{A as _,F as v,M as y,S as b,j as x,p as S}from"./index-BmfaaNx6.js";import{t as C}from"./page-header-BYMFSGfT.js";import{t as w}from"./alert-CSXqgDVi.js";import{t as T}from"./promise-OpBtq8tG.js";import{r as E,t as D}from"./standard-schema-o4V-s4uY.js";import{t as O}from"./icon-input-8iU7PNzd.js";import{t as k}from"./submit-button-Xx6DwLyh.js";var A=r(n()),j=e();function M({text:e,className:t=``}){return(0,j.jsxs)(`div`,{className:`my-4 flex items-center ${t}`,children:[(0,j.jsx)(`div`,{className:`h-px flex-1 bg-base-200`}),e&&(0,j.jsx)(`span`,{className:`px-3 text-base-content/60 text-sm`,children:e}),(0,j.jsx)(`div`,{className:`h-px flex-1 bg-base-200`})]})}var N=u({mutationFn:async e=>a(await o.api.auth.email.verify.$post({json:e}))}),P=u({mutationFn:async e=>a(await o.api.auth.email.resend.$post({json:e,header:{}}))});function F(){let{t:e}=t(),n=v(),r=c(),a=S.useSearch(),{token:o,email:u}=a,[F,I]=(0,A.useState)(!1),[L,R]=(0,A.useState)(!1),{data:z}=i(s),B=(0,A.useMemo)(()=>g({token:h().min(1,e(`validation.token.required`))}),[e]),V=l({...N,onSuccess:async e=>{let t=e.user;if(r.setQueryData(b.queryKey,{user:t}),await T(),t.second_factor_required){let e=[];return z.auth.password.totp.enabled&&e.push(`totp`),z.auth.passkey.enabled&&e.push(`passkey`),e.length===1?e[0]===`totp`?n({to:`/setup/totp`,search:x(a)}):n({to:`/setup/passkey`,search:x(a)}):n({to:`/setup/2fa`,search:x(a)})}y(a)?window.location.href=_(a):I(!0)},onSettled:()=>{r.invalidateQueries({queryKey:b.queryKey})}}),H=l({...P,onSuccess:()=>{R(!0),setTimeout(()=>R(!1),5e3)}}),{register:U,setError:W,handleSubmit:G,formState:{errors:K}}=E({defaultValues:{token:o||``},resolver:D(B)}),q=async t=>{try{await V.mutateAsync(t)}catch{W(`token`,{type:`manual`,message:e(`verifyEmail.error.invalidToken`)})}},J=async()=>{if(u)try{await H.mutateAsync({email:u})}catch{}};return F?(0,j.jsxs)(f,{cardPadding:!0,maxWidth:`100`,children:[(0,j.jsx)(w,{className:`mb-4`,icon:d,type:`success`,children:e(`verifyEmail.success.title`)}),(0,j.jsx)(C,{subtitle:e(`verifyEmail.success.description`),title:e(`verifyEmail.success.subtitle`)}),(0,j.jsx)(`button`,{className:`btn btn-block h-10 font-semibold text-[14px]`,"data-testid":`email-verify-go-profile`,onClick:()=>n({to:`/profile`}),type:`button`,children:e(`verifyEmail.success.goToProfile`)})]}):(0,j.jsxs)(f,{cardPadding:!0,maxWidth:`100`,children:[(0,j.jsx)(C,{subtitle:e(`verifyEmail.subtitle`),title:e(`verifyEmail.title`)}),u&&(0,j.jsx)(w,{className:`mb-4`,icon:p,type:`info`,children:(0,j.jsxs)(`div`,{className:`text-left`,children:[(0,j.jsx)(`p`,{className:`font-semibold`,children:e(`register.success.subtitle`)}),(0,j.jsx)(`p`,{className:`text-xs`,children:e(`register.success.description`,{email:u})})]})}),(0,j.jsxs)(`form`,{className:`flex flex-col gap-4`,onSubmit:G(q),children:[(0,j.jsx)(O,{error:K.token,icon:m,placeholder:e(`verifyEmail.token.placeholder`),...U(`token`),type:`text`}),(0,j.jsx)(k,{className:`mt-2`,isPending:V.isPending,pendingText:e(`verifyEmail.submitting`),children:e(`verifyEmail.submit`)})]}),u&&(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(M,{}),L&&(0,j.jsx)(w,{className:`mb-2`,icon:d,type:`success`,children:e(`verifyEmail.resendSuccess`)}),(0,j.jsx)(`button`,{className:`btn btn-ghost btn-sm w-full`,"data-testid":`email-verify-resend`,disabled:H.isPending||L,onClick:J,type:`button`,children:H.isPending?(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(`span`,{className:`loading loading-spinner loading-xs`}),e(`verifyEmail.resending`)]}):e(`verifyEmail.resend`)})]})]})}export{F as component};
2
+ //# sourceMappingURL=email-DjE7mqnX.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-CIttZRBe.js","names":["standardSchemaResolver","CheckCircleIcon","EnvelopeSimpleIcon","KeyIcon","useMutation","useQueryClient","useSuspenseQuery","useNavigate","useMemo","useState","useForm","useTranslation","z","IconInput","PageHeader","SubmitButton","Alert","Divider","PageLayout","buildAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","appConfigQueryOptions","getSessionQueryOptions","resendVerificationMutationOptions","verifyEmailMutationOptions","Route","VerifyEmailFormValues","token","VerifyEmail","t","navigate","queryClient","search","useSearch","queryToken","email","verified","setVerified","resendSuccess","setResendSuccess","data","appConfig","verifyEmailSchema","object","string","min","verifyEmailMutation","onSuccess","user","setQueryData","queryKey","second_factor_required","available_2fa_methods","SecondFactorMethod","auth","password","totp","enabled","push","passkey","length","method","to","window","location","href","onSettled","invalidateQueries","resendVerificationMutation","setTimeout","register","setError","handleSubmit","formState","errors","defaultValues","resolver","onSubmit","values","mutateAsync","_error","type","message","handleResend","isPending","component"],"sources":["../../../frontend/src/components/ui/divider.tsx","../../../frontend/src/queries/verify-email.ts","../../../frontend/src/routes/verify/email/index.tsx?tsr-split=component"],"sourcesContent":["type DividerProps = {\n text?: string;\n className?: string;\n};\n\nexport function Divider({ text, className = '' }: DividerProps) {\n return (\n <div className={`my-4 flex items-center ${className}`}>\n <div className=\"h-px flex-1 bg-base-200\" />\n {text && (\n <span className=\"px-3 text-base-content/60 text-sm\">{text}</span>\n )}\n <div className=\"h-px flex-1 bg-base-200\" />\n </div>\n );\n}\n","import { mutationOptions } from '@tanstack/react-query';\nimport type { InferRequestType, InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\n\nexport type VerifyEmailParams = InferRequestType<\n (typeof client.api.auth.email.verify)['$post']\n>['json'];\n\nexport type VerifyEmailResponse = InferResponseType<\n (typeof client.api.auth.email.verify)['$post'],\n 200\n>;\n\nexport const verifyEmailMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyEmailParams) => {\n const res = await client.api.auth.email.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\nexport type ResendVerificationParams = InferRequestType<\n (typeof client.api.auth.email.resend)['$post']\n>['json'];\n\nexport type ResendVerificationResponse = InferResponseType<\n (typeof client.api.auth.email.resend)['$post'],\n 200\n>;\n\nexport const resendVerificationMutationOptions = mutationOptions({\n mutationFn: async (values: ResendVerificationParams) => {\n const res = await client.api.auth.email.resend.$post({\n json: values,\n header: {},\n });\n return jsonOk(res);\n },\n});\n","import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n CheckCircleIcon,\n EnvelopeSimpleIcon,\n KeyIcon,\n} from '@phosphor-icons/react';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { Divider } from '#frontend/components/ui/divider.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} 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 resendVerificationMutationOptions,\n verifyEmailMutationOptions,\n} from '#frontend/queries/verify-email.ts';\n\nconst SearchSchema = z.object({\n ...OAuthSearchSchema.shape,\n token: z.string().default(''),\n email: z.string().default(''),\n});\n\nexport const Route = createFileRoute('/verify/email/')({\n component: VerifyEmail,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype VerifyEmailFormValues = {\n token: string;\n};\n\nfunction VerifyEmail() {\n const { t } = useTranslation();\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { token: queryToken, email } = search;\n const [verified, setVerified] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const verifyEmailSchema = useMemo(\n () =>\n z.object({\n token: z.string().min(1, t('validation.token.required')),\n }),\n [t],\n );\n\n const verifyEmailMutation = useMutation({\n ...verifyEmailMutationOptions,\n onSuccess: async (data) => {\n const user = data.user;\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n if (user.second_factor_required) {\n const available_2fa_methods: SecondFactorMethod[] = [];\n if (appConfig.auth.password.totp.enabled) {\n available_2fa_methods.push('totp');\n }\n if (appConfig.auth.passkey.enabled) {\n available_2fa_methods.push('passkey');\n }\n\n if (available_2fa_methods.length === 1) {\n const method = available_2fa_methods[0];\n if (method === 'totp') {\n return navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return navigate({\n to: '/setup/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n return navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n }\n }\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthorizeUrl(search);\n } else {\n setVerified(true);\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const resendVerificationMutation = useMutation({\n ...resendVerificationMutationOptions,\n onSuccess: () => {\n setResendSuccess(true);\n setTimeout(() => setResendSuccess(false), 5000);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<VerifyEmailFormValues>({\n defaultValues: {\n token: queryToken || '',\n },\n resolver: standardSchemaResolver(verifyEmailSchema),\n });\n\n const onSubmit = async (values: VerifyEmailFormValues) => {\n try {\n await verifyEmailMutation.mutateAsync(values);\n } catch (_error) {\n setError('token', {\n type: 'manual',\n message: t('verifyEmail.error.invalidToken'),\n });\n }\n };\n\n const handleResend = async () => {\n if (!email) {\n return;\n }\n try {\n await resendVerificationMutation.mutateAsync({ email });\n } catch (_error) {}\n };\n\n if (verified) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('verifyEmail.success.description')}\n title={t('verifyEmail.success.subtitle')}\n />\n\n <button\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n data-testid=\"email-verify-go-profile\"\n onClick={() => navigate({ to: '/profile' })}\n type=\"button\"\n >\n {t('verifyEmail.success.goToProfile')}\n </button>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('verifyEmail.subtitle')}\n title={t('verifyEmail.title')}\n />\n\n {email && (\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n <div className=\"text-left\">\n <p className=\"font-semibold\">{t('register.success.subtitle')}</p>\n <p className=\"text-xs\">\n {t('register.success.description', { email })}\n </p>\n </div>\n </Alert>\n )}\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n error={errors.token}\n icon={KeyIcon}\n placeholder={t('verifyEmail.token.placeholder')}\n {...register('token')}\n type=\"text\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={verifyEmailMutation.isPending}\n pendingText={t('verifyEmail.submitting')}\n >\n {t('verifyEmail.submit')}\n </SubmitButton>\n </form>\n\n {email && (\n <>\n <Divider />\n\n {resendSuccess && (\n <Alert className=\"mb-2\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.resendSuccess')}\n </Alert>\n )}\n\n <button\n className=\"btn btn-ghost btn-sm w-full\"\n data-testid=\"email-verify-resend\"\n disabled={resendVerificationMutation.isPending || resendSuccess}\n onClick={handleResend}\n type=\"button\"\n >\n {resendVerificationMutation.isPending ? (\n <>\n <span className=\"loading loading-spinner loading-xs\" />\n {t('verifyEmail.resending')}\n </>\n ) : (\n t('verifyEmail.resend')\n )}\n </button>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"email-DjE7mqnX.js","names":["standardSchemaResolver","CheckCircleIcon","EnvelopeSimpleIcon","KeyIcon","useMutation","useQueryClient","useSuspenseQuery","useNavigate","useMemo","useState","useForm","useTranslation","z","IconInput","PageHeader","SubmitButton","Alert","Divider","PageLayout","buildAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","appConfigQueryOptions","getSessionQueryOptions","resendVerificationMutationOptions","verifyEmailMutationOptions","Route","VerifyEmailFormValues","token","VerifyEmail","t","navigate","queryClient","search","useSearch","queryToken","email","verified","setVerified","resendSuccess","setResendSuccess","data","appConfig","verifyEmailSchema","object","string","min","verifyEmailMutation","onSuccess","user","setQueryData","queryKey","second_factor_required","available_2fa_methods","SecondFactorMethod","auth","password","totp","enabled","push","passkey","length","method","to","window","location","href","onSettled","invalidateQueries","resendVerificationMutation","setTimeout","register","setError","handleSubmit","formState","errors","defaultValues","resolver","onSubmit","values","mutateAsync","_error","type","message","handleResend","isPending","component"],"sources":["../../../frontend/src/components/ui/divider.tsx","../../../frontend/src/queries/verify-email.ts","../../../frontend/src/routes/verify/email/index.tsx?tsr-split=component"],"sourcesContent":["type DividerProps = {\n text?: string;\n className?: string;\n};\n\nexport function Divider({ text, className = '' }: DividerProps) {\n return (\n <div className={`my-4 flex items-center ${className}`}>\n <div className=\"h-px flex-1 bg-base-200\" />\n {text && (\n <span className=\"px-3 text-base-content/60 text-sm\">{text}</span>\n )}\n <div className=\"h-px flex-1 bg-base-200\" />\n </div>\n );\n}\n","import { mutationOptions } from '@tanstack/react-query';\nimport type { InferRequestType, InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\n\nexport type VerifyEmailParams = InferRequestType<\n (typeof client.api.auth.email.verify)['$post']\n>['json'];\n\nexport type VerifyEmailResponse = InferResponseType<\n (typeof client.api.auth.email.verify)['$post'],\n 200\n>;\n\nexport const verifyEmailMutationOptions = mutationOptions({\n mutationFn: async (values: VerifyEmailParams) => {\n const res = await client.api.auth.email.verify.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n\nexport type ResendVerificationParams = InferRequestType<\n (typeof client.api.auth.email.resend)['$post']\n>['json'];\n\nexport type ResendVerificationResponse = InferResponseType<\n (typeof client.api.auth.email.resend)['$post'],\n 200\n>;\n\nexport const resendVerificationMutationOptions = mutationOptions({\n mutationFn: async (values: ResendVerificationParams) => {\n const res = await client.api.auth.email.resend.$post({\n json: values,\n header: {},\n });\n return jsonOk(res);\n },\n});\n","import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n CheckCircleIcon,\n EnvelopeSimpleIcon,\n KeyIcon,\n} from '@phosphor-icons/react';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useNavigate } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { Divider } from '#frontend/components/ui/divider.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport {\n buildAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} 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 resendVerificationMutationOptions,\n verifyEmailMutationOptions,\n} from '#frontend/queries/verify-email.ts';\n\nconst SearchSchema = z.object({\n ...OAuthSearchSchema.shape,\n token: z.string().default(''),\n email: z.string().default(''),\n});\n\nexport const Route = createFileRoute('/verify/email/')({\n component: VerifyEmail,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype VerifyEmailFormValues = {\n token: string;\n};\n\nfunction VerifyEmail() {\n const { t } = useTranslation();\n const navigate = useNavigate();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { token: queryToken, email } = search;\n const [verified, setVerified] = useState(false);\n const [resendSuccess, setResendSuccess] = useState(false);\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const verifyEmailSchema = useMemo(\n () =>\n z.object({\n token: z.string().min(1, t('validation.token.required')),\n }),\n [t],\n );\n\n const verifyEmailMutation = useMutation({\n ...verifyEmailMutationOptions,\n onSuccess: async (data) => {\n const user = data.user;\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n if (user.second_factor_required) {\n const available_2fa_methods: SecondFactorMethod[] = [];\n if (appConfig.auth.password.totp.enabled) {\n available_2fa_methods.push('totp');\n }\n if (appConfig.auth.passkey.enabled) {\n available_2fa_methods.push('passkey');\n }\n\n if (available_2fa_methods.length === 1) {\n const method = available_2fa_methods[0];\n if (method === 'totp') {\n return navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return navigate({\n to: '/setup/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n return navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n }\n }\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthorizeUrl(search);\n } else {\n setVerified(true);\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const resendVerificationMutation = useMutation({\n ...resendVerificationMutationOptions,\n onSuccess: () => {\n setResendSuccess(true);\n setTimeout(() => setResendSuccess(false), 5000);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<VerifyEmailFormValues>({\n defaultValues: {\n token: queryToken || '',\n },\n resolver: standardSchemaResolver(verifyEmailSchema),\n });\n\n const onSubmit = async (values: VerifyEmailFormValues) => {\n try {\n await verifyEmailMutation.mutateAsync(values);\n } catch (_error) {\n setError('token', {\n type: 'manual',\n message: t('verifyEmail.error.invalidToken'),\n });\n }\n };\n\n const handleResend = async () => {\n if (!email) {\n return;\n }\n try {\n await resendVerificationMutation.mutateAsync({ email });\n } catch (_error) {}\n };\n\n if (verified) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('verifyEmail.success.description')}\n title={t('verifyEmail.success.subtitle')}\n />\n\n <button\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n data-testid=\"email-verify-go-profile\"\n onClick={() => navigate({ to: '/profile' })}\n type=\"button\"\n >\n {t('verifyEmail.success.goToProfile')}\n </button>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('verifyEmail.subtitle')}\n title={t('verifyEmail.title')}\n />\n\n {email && (\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n <div className=\"text-left\">\n <p className=\"font-semibold\">{t('register.success.subtitle')}</p>\n <p className=\"text-xs\">\n {t('register.success.description', { email })}\n </p>\n </div>\n </Alert>\n )}\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n error={errors.token}\n icon={KeyIcon}\n placeholder={t('verifyEmail.token.placeholder')}\n {...register('token')}\n type=\"text\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={verifyEmailMutation.isPending}\n pendingText={t('verifyEmail.submitting')}\n >\n {t('verifyEmail.submit')}\n </SubmitButton>\n </form>\n\n {email && (\n <>\n <Divider />\n\n {resendSuccess && (\n <Alert className=\"mb-2\" icon={CheckCircleIcon} type=\"success\">\n {t('verifyEmail.resendSuccess')}\n </Alert>\n )}\n\n <button\n className=\"btn btn-ghost btn-sm w-full\"\n data-testid=\"email-verify-resend\"\n disabled={resendVerificationMutation.isPending || resendSuccess}\n onClick={handleResend}\n type=\"button\"\n >\n {resendVerificationMutation.isPending ? (\n <>\n <span className=\"loading loading-spinner loading-xs\" />\n {t('verifyEmail.resending')}\n </>\n ) : (\n t('verifyEmail.resend')\n )}\n </button>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{o as e,r as t,s as n,t as r,u as i}from"./IconBase.es-d5KP98Ac.js";import{f as a}from"./use-theme-cVUDAjtt.js";import{t as o}from"./page-layout-C475gs09.js";import{I as s,P as c}from"./index-CsT6OVnP.js";import{t as l}from"./page-header-BYMFSGfT.js";import{t as u}from"./alert-CSXqgDVi.js";var d=i(n(),1),f=new Map([[`bold`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z`}))],[`duotone`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M216,120v96H152V152H104v64H40V120a8,8,0,0,1,2.34-5.66l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,216,120Z`,opacity:`0.2`}),d.createElement(`path`,{d:`M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z`}))],[`fill`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z`}))],[`light`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M217.9,110.1l-80-80a14,14,0,0,0-19.8,0l-80,80A13.92,13.92,0,0,0,34,120v96a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V158h36v58a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V120A13.92,13.92,0,0,0,217.9,110.1ZM210,210H158V152a6,6,0,0,0-6-6H104a6,6,0,0,0-6,6v58H46V120a2,2,0,0,1,.58-1.42l80-80a2,2,0,0,1,2.84,0l80,80A2,2,0,0,1,210,120Z`}))],[`regular`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z`}))],[`thin`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M216.49,111.51l-80-80a12,12,0,0,0-17,0l-80,80A12,12,0,0,0,36,120v96a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V156h40v60a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V120A12,12,0,0,0,216.49,111.51ZM212,212H156V152a4,4,0,0,0-4-4H104a4,4,0,0,0-4,4v60H44V120a4,4,0,0,1,1.17-2.83l80-80a4,4,0,0,1,5.66,0l80,80A4,4,0,0,1,212,120Z`}))]]),p=d.forwardRef((e,t)=>d.createElement(r,{ref:t,...e,weights:f}));p.displayName=`HouseIcon`;var m=e();function h(){let{t:e}=t(),n=s({from:`/error/`}),r=n.code||`UNKNOWN_ERROR`,i=n.message||e(`error.defaultMessage`);return(0,m.jsxs)(o,{cardPadding:!0,maxWidth:`100`,children:[(0,m.jsx)(u,{className:`mb-4`,icon:c,type:`error`,children:e(`error.title`)}),(0,m.jsx)(l,{subtitle:i,title:e(`error.subtitle`)}),(0,m.jsxs)(`div`,{className:`mb-6 rounded-lg bg-base-200 p-4 text-center`,children:[(0,m.jsx)(`p`,{className:`mb-1 text-base-content/50 text-xs`,children:e(`error.codeLabel`)}),(0,m.jsx)(`code`,{className:`font-mono text-error text-sm`,"data-testid":`error-code`,children:r})]}),(0,m.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,m.jsx)(a,{className:`btn btn-block h-10 font-semibold text-[14px]`,to:`/login`,children:e(`error.goToLogin`)}),(0,m.jsxs)(`button`,{className:`btn btn-ghost btn-block h-10 font-semibold text-[14px]`,onClick:()=>window.history.back(),type:`button`,children:[(0,m.jsx)(p,{className:`size-4`,weight:`fill`}),e(`error.goBack`)]})]}),(0,m.jsxs)(`div`,{className:`mt-6 text-center text-base-content/70 text-xs`,children:[e(`error.footer.needHelp`),` `,(0,m.jsx)(`a`,{className:`link link-info font-medium`,href:`mailto:support@example.com`,children:e(`error.footer.contactSupport`)})]})]})}export{h as component};
2
- //# sourceMappingURL=error-D60wkdWN.js.map
1
+ import{o as e,r as t,s as n,t as r,u as i}from"./IconBase.es-d5KP98Ac.js";import{f as a}from"./use-theme-cVUDAjtt.js";import{t as o}from"./page-layout-C475gs09.js";import{I as s,P as c}from"./index-BmfaaNx6.js";import{t as l}from"./page-header-BYMFSGfT.js";import{t as u}from"./alert-CSXqgDVi.js";var d=i(n(),1),f=new Map([[`bold`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z`}))],[`duotone`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M216,120v96H152V152H104v64H40V120a8,8,0,0,1,2.34-5.66l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,216,120Z`,opacity:`0.2`}),d.createElement(`path`,{d:`M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z`}))],[`fill`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z`}))],[`light`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M217.9,110.1l-80-80a14,14,0,0,0-19.8,0l-80,80A13.92,13.92,0,0,0,34,120v96a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V158h36v58a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V120A13.92,13.92,0,0,0,217.9,110.1ZM210,210H158V152a6,6,0,0,0-6-6H104a6,6,0,0,0-6,6v58H46V120a2,2,0,0,1,.58-1.42l80-80a2,2,0,0,1,2.84,0l80,80A2,2,0,0,1,210,120Z`}))],[`regular`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z`}))],[`thin`,d.createElement(d.Fragment,null,d.createElement(`path`,{d:`M216.49,111.51l-80-80a12,12,0,0,0-17,0l-80,80A12,12,0,0,0,36,120v96a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V156h40v60a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V120A12,12,0,0,0,216.49,111.51ZM212,212H156V152a4,4,0,0,0-4-4H104a4,4,0,0,0-4,4v60H44V120a4,4,0,0,1,1.17-2.83l80-80a4,4,0,0,1,5.66,0l80,80A4,4,0,0,1,212,120Z`}))]]),p=d.forwardRef((e,t)=>d.createElement(r,{ref:t,...e,weights:f}));p.displayName=`HouseIcon`;var m=e();function h(){let{t:e}=t(),n=s({from:`/error/`}),r=n.code||`UNKNOWN_ERROR`,i=n.message||e(`error.defaultMessage`);return(0,m.jsxs)(o,{cardPadding:!0,maxWidth:`100`,children:[(0,m.jsx)(u,{className:`mb-4`,icon:c,type:`error`,children:e(`error.title`)}),(0,m.jsx)(l,{subtitle:i,title:e(`error.subtitle`)}),(0,m.jsxs)(`div`,{className:`mb-6 rounded-lg bg-base-200 p-4 text-center`,children:[(0,m.jsx)(`p`,{className:`mb-1 text-base-content/50 text-xs`,children:e(`error.codeLabel`)}),(0,m.jsx)(`code`,{className:`font-mono text-error text-sm`,"data-testid":`error-code`,children:r})]}),(0,m.jsxs)(`div`,{className:`flex flex-col gap-3`,children:[(0,m.jsx)(a,{className:`btn btn-block h-10 font-semibold text-[14px]`,to:`/login`,children:e(`error.goToLogin`)}),(0,m.jsxs)(`button`,{className:`btn btn-ghost btn-block h-10 font-semibold text-[14px]`,onClick:()=>window.history.back(),type:`button`,children:[(0,m.jsx)(p,{className:`size-4`,weight:`fill`}),e(`error.goBack`)]})]}),(0,m.jsxs)(`div`,{className:`mt-6 text-center text-base-content/70 text-xs`,children:[e(`error.footer.needHelp`),` `,(0,m.jsx)(`a`,{className:`link link-info font-medium`,href:`mailto:support@example.com`,children:e(`error.footer.contactSupport`)})]})]})}export{h as component};
2
+ //# sourceMappingURL=error-BKXsXhdR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-D60wkdWN.js","names":["e","t","a","HouseIcon","WarningCircleIcon","Link","useSearch","useTranslation","PageHeader","Alert","PageLayout","ErrorPage","t","search","from","errorCode","code","errorMessage","message","window","history","back","component"],"sources":["../../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@phosphor-icons/react/dist/defs/House.es.js","../../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@phosphor-icons/react/dist/csr/House.es.js","../../../frontend/src/routes/error/index.tsx?tsr-split=component"],"sourcesContent":["import * as a from \"react\";\nconst e = /* @__PURE__ */ new Map([\n [\n \"bold\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z\" }))\n ],\n [\n \"duotone\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\n \"path\",\n {\n d: \"M216,120v96H152V152H104v64H40V120a8,8,0,0,1,2.34-5.66l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,216,120Z\",\n opacity: \"0.2\"\n }\n ), /* @__PURE__ */ a.createElement(\"path\", { d: \"M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z\" }))\n ],\n [\n \"fill\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z\" }))\n ],\n [\n \"light\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M217.9,110.1l-80-80a14,14,0,0,0-19.8,0l-80,80A13.92,13.92,0,0,0,34,120v96a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V158h36v58a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V120A13.92,13.92,0,0,0,217.9,110.1ZM210,210H158V152a6,6,0,0,0-6-6H104a6,6,0,0,0-6,6v58H46V120a2,2,0,0,1,.58-1.42l80-80a2,2,0,0,1,2.84,0l80,80A2,2,0,0,1,210,120Z\" }))\n ],\n [\n \"regular\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z\" }))\n ],\n [\n \"thin\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M216.49,111.51l-80-80a12,12,0,0,0-17,0l-80,80A12,12,0,0,0,36,120v96a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V156h40v60a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V120A12,12,0,0,0,216.49,111.51ZM212,212H156V152a4,4,0,0,0-4-4H104a4,4,0,0,0-4,4v60H44V120a4,4,0,0,1,1.17-2.83l80-80a4,4,0,0,1,5.66,0l80,80A4,4,0,0,1,212,120Z\" }))\n ]\n]);\nexport {\n e as default\n};\n","import * as o from \"react\";\nimport t from \"../lib/IconBase.es.js\";\nimport a from \"../defs/House.es.js\";\nconst e = o.forwardRef((r, s) => /* @__PURE__ */ o.createElement(t, { ref: s, ...r, weights: a }));\ne.displayName = \"HouseIcon\";\nconst n = e;\nexport {\n n as House,\n e as HouseIcon\n};\n","import { HouseIcon, WarningCircleIcon } from '@phosphor-icons/react';\nimport { createFileRoute, Link, useSearch } from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\n\nconst errorSearchSchema = z.object({\n code: z.string().optional(),\n message: z.string().optional(),\n});\n\nexport const Route = createFileRoute('/error/')({\n component: ErrorPage,\n validateSearch: errorSearchSchema,\n});\n\nfunction ErrorPage() {\n const { t } = useTranslation();\n const search = useSearch({ from: '/error/' });\n\n const errorCode = search.code || 'UNKNOWN_ERROR';\n const errorMessage = search.message || t('error.defaultMessage');\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={WarningCircleIcon} type=\"error\">\n {t('error.title')}\n </Alert>\n\n <PageHeader subtitle={errorMessage} title={t('error.subtitle')} />\n\n {/* Error Code */}\n <div className=\"mb-6 rounded-lg bg-base-200 p-4 text-center\">\n <p className=\"mb-1 text-base-content/50 text-xs\">\n {t('error.codeLabel')}\n </p>\n <code className=\"font-mono text-error text-sm\" data-testid=\"error-code\">\n {errorCode}\n </code>\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-3\">\n <Link\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n to=\"/login\"\n >\n {t('error.goToLogin')}\n </Link>\n <button\n className=\"btn btn-ghost btn-block h-10 font-semibold text-[14px]\"\n onClick={() => window.history.back()}\n type=\"button\"\n >\n <HouseIcon className=\"size-4\" weight=\"fill\" />\n {t('error.goBack')}\n </button>\n </div>\n\n {/* Footer */}\n <div className=\"mt-6 text-center text-base-content/70 text-xs\">\n {t('error.footer.needHelp')}{' '}\n <a\n className=\"link link-info font-medium\"\n href=\"mailto:support@example.com\"\n >\n {t('error.footer.contactSupport')}\n </a>\n </div>\n </PageLayout>\n );\n}\n"],"x_google_ignoreList":[0,1],"mappings":"wTACMA,EAAoB,IAAI,IAAI,CAChC,CACE,OACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,6RAA8R,CAAC,CAAC,CACjY,EACA,CACE,UACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAClE,OACA,CACE,EAAG,yGACH,QAAS,KACX,CACF,EAAmB,EAAE,cAAc,OAAQ,CAAE,EAAG,kQAAmQ,CAAC,CAAC,CACvT,EACA,CACE,OACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,mMAAoM,CAAC,CAAC,CACvS,EACA,CACE,QACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,mTAAoT,CAAC,CAAC,CACvZ,EACA,CACE,UACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,kQAAmQ,CAAC,CAAC,CACtW,EACA,CACE,OACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,0SAA2S,CAAC,CAAC,CAC9Y,CACF,CAAC,EC7BK,EAAA,EAAM,YAAY,EAAG,IAAsB,EAAE,cAAcC,EAAG,CAAE,IAAK,EAAG,GAAG,EAAG,QAASC,CAAE,CAAC,CAAC,EACjG,EAAE,YAAc,sBCchB,SAASS,GAAY,CACnB,GAAM,CAAEC,KAAML,EAAe,EACvBM,EAASP,EAAU,CAAEQ,KAAM,SAAU,CAAC,EAEtCC,EAAYF,EAAOG,MAAQ,gBAC3BC,EAAeJ,EAAOK,SAAWN,EAAE,sBAAsB,EAE/D,OACE,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,OAAO,KAAMR,EAAmB,KAAK,iBACnDQ,EAAE,aAAa,CACX,CAAA,GAEP,EAAA,EAAA,KAAC,EAAD,CAAY,SAAUK,EAAc,MAAOL,EAAE,gBAAgB,CAAE,CAAA,GAG/D,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,uDAAf,EACE,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,6CACVA,EAAE,iBAAiB,CACnB,CAAA,GACH,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,+BAA+B,cAAY,sBACxDG,CACG,CAAA,CACH,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,+BAAf,EACE,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,+CACV,GAAG,kBAEFH,EAAE,iBAAiB,CAChB,CAAA,GACN,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,yDACV,YAAeO,OAAOC,QAAQC,KAAK,EACnC,KAAK,kBAHP,EAKE,EAAA,EAAA,KAAC,EAAD,CAAW,UAAU,SAAS,OAAO,MAAM,CAAA,EAC1CT,EAAE,cAAc,CACX,GACL,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,yDAAf,CACGA,EAAE,uBAAuB,EAAG,KAC7B,EAAA,EAAA,KAAC,IAAD,CACE,UAAU,6BACV,KAAK,sCAEJA,EAAE,6BAA6B,CAC/B,CAAA,CACA,GACK,GAEhB"}
1
+ {"version":3,"file":"error-BKXsXhdR.js","names":["e","t","a","HouseIcon","WarningCircleIcon","Link","useSearch","useTranslation","PageHeader","Alert","PageLayout","ErrorPage","t","search","from","errorCode","code","errorMessage","message","window","history","back","component"],"sources":["../../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@phosphor-icons/react/dist/defs/House.es.js","../../../../node_modules/.pnpm/@phosphor-icons+react@2.1.10_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@phosphor-icons/react/dist/csr/House.es.js","../../../frontend/src/routes/error/index.tsx?tsr-split=component"],"sourcesContent":["import * as a from \"react\";\nconst e = /* @__PURE__ */ new Map([\n [\n \"bold\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M222.14,105.85l-80-80a20,20,0,0,0-28.28,0l-80,80A19.86,19.86,0,0,0,28,120v96a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V164h24v52a12,12,0,0,0,12,12h64a12,12,0,0,0,12-12V120A19.86,19.86,0,0,0,222.14,105.85ZM204,204H164V152a12,12,0,0,0-12-12H104a12,12,0,0,0-12,12v52H52V121.65l76-76,76,76Z\" }))\n ],\n [\n \"duotone\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\n \"path\",\n {\n d: \"M216,120v96H152V152H104v64H40V120a8,8,0,0,1,2.34-5.66l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,216,120Z\",\n opacity: \"0.2\"\n }\n ), /* @__PURE__ */ a.createElement(\"path\", { d: \"M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z\" }))\n ],\n [\n \"fill\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z\" }))\n ],\n [\n \"light\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M217.9,110.1l-80-80a14,14,0,0,0-19.8,0l-80,80A13.92,13.92,0,0,0,34,120v96a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V158h36v58a6,6,0,0,0,6,6h64a6,6,0,0,0,6-6V120A13.92,13.92,0,0,0,217.9,110.1ZM210,210H158V152a6,6,0,0,0-6-6H104a6,6,0,0,0-6,6v58H46V120a2,2,0,0,1,.58-1.42l80-80a2,2,0,0,1,2.84,0l80,80A2,2,0,0,1,210,120Z\" }))\n ],\n [\n \"regular\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M219.31,108.68l-80-80a16,16,0,0,0-22.62,0l-80,80A15.87,15.87,0,0,0,32,120v96a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V160h32v56a8,8,0,0,0,8,8h64a8,8,0,0,0,8-8V120A15.87,15.87,0,0,0,219.31,108.68ZM208,208H160V152a8,8,0,0,0-8-8H104a8,8,0,0,0-8,8v56H48V120l80-80,80,80Z\" }))\n ],\n [\n \"thin\",\n /* @__PURE__ */ a.createElement(a.Fragment, null, /* @__PURE__ */ a.createElement(\"path\", { d: \"M216.49,111.51l-80-80a12,12,0,0,0-17,0l-80,80A12,12,0,0,0,36,120v96a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V156h40v60a4,4,0,0,0,4,4h64a4,4,0,0,0,4-4V120A12,12,0,0,0,216.49,111.51ZM212,212H156V152a4,4,0,0,0-4-4H104a4,4,0,0,0-4,4v60H44V120a4,4,0,0,1,1.17-2.83l80-80a4,4,0,0,1,5.66,0l80,80A4,4,0,0,1,212,120Z\" }))\n ]\n]);\nexport {\n e as default\n};\n","import * as o from \"react\";\nimport t from \"../lib/IconBase.es.js\";\nimport a from \"../defs/House.es.js\";\nconst e = o.forwardRef((r, s) => /* @__PURE__ */ o.createElement(t, { ref: s, ...r, weights: a }));\ne.displayName = \"HouseIcon\";\nconst n = e;\nexport {\n n as House,\n e as HouseIcon\n};\n","import { HouseIcon, WarningCircleIcon } from '@phosphor-icons/react';\nimport { createFileRoute, Link, useSearch } from '@tanstack/react-router';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\n\nconst errorSearchSchema = z.object({\n code: z.string().optional(),\n message: z.string().optional(),\n});\n\nexport const Route = createFileRoute('/error/')({\n component: ErrorPage,\n validateSearch: errorSearchSchema,\n});\n\nfunction ErrorPage() {\n const { t } = useTranslation();\n const search = useSearch({ from: '/error/' });\n\n const errorCode = search.code || 'UNKNOWN_ERROR';\n const errorMessage = search.message || t('error.defaultMessage');\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={WarningCircleIcon} type=\"error\">\n {t('error.title')}\n </Alert>\n\n <PageHeader subtitle={errorMessage} title={t('error.subtitle')} />\n\n {/* Error Code */}\n <div className=\"mb-6 rounded-lg bg-base-200 p-4 text-center\">\n <p className=\"mb-1 text-base-content/50 text-xs\">\n {t('error.codeLabel')}\n </p>\n <code className=\"font-mono text-error text-sm\" data-testid=\"error-code\">\n {errorCode}\n </code>\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-3\">\n <Link\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n to=\"/login\"\n >\n {t('error.goToLogin')}\n </Link>\n <button\n className=\"btn btn-ghost btn-block h-10 font-semibold text-[14px]\"\n onClick={() => window.history.back()}\n type=\"button\"\n >\n <HouseIcon className=\"size-4\" weight=\"fill\" />\n {t('error.goBack')}\n </button>\n </div>\n\n {/* Footer */}\n <div className=\"mt-6 text-center text-base-content/70 text-xs\">\n {t('error.footer.needHelp')}{' '}\n <a\n className=\"link link-info font-medium\"\n href=\"mailto:support@example.com\"\n >\n {t('error.footer.contactSupport')}\n </a>\n </div>\n </PageLayout>\n );\n}\n"],"x_google_ignoreList":[0,1],"mappings":"wTACMA,EAAoB,IAAI,IAAI,CAChC,CACE,OACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,6RAA8R,CAAC,CAAC,CACjY,EACA,CACE,UACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAClE,OACA,CACE,EAAG,yGACH,QAAS,KACX,CACF,EAAmB,EAAE,cAAc,OAAQ,CAAE,EAAG,kQAAmQ,CAAC,CAAC,CACvT,EACA,CACE,OACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,mMAAoM,CAAC,CAAC,CACvS,EACA,CACE,QACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,mTAAoT,CAAC,CAAC,CACvZ,EACA,CACE,UACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,kQAAmQ,CAAC,CAAC,CACtW,EACA,CACE,OACgB,EAAE,cAAA,EAAgB,SAAU,KAAsB,EAAE,cAAc,OAAQ,CAAE,EAAG,0SAA2S,CAAC,CAAC,CAC9Y,CACF,CAAC,EC7BK,EAAA,EAAM,YAAY,EAAG,IAAsB,EAAE,cAAcC,EAAG,CAAE,IAAK,EAAG,GAAG,EAAG,QAASC,CAAE,CAAC,CAAC,EACjG,EAAE,YAAc,sBCchB,SAASS,GAAY,CACnB,GAAM,CAAEC,KAAML,EAAe,EACvBM,EAASP,EAAU,CAAEQ,KAAM,SAAU,CAAC,EAEtCC,EAAYF,EAAOG,MAAQ,gBAC3BC,EAAeJ,EAAOK,SAAWN,EAAE,sBAAsB,EAE/D,OACE,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,OAAO,KAAMR,EAAmB,KAAK,iBACnDQ,EAAE,aAAa,CACX,CAAA,GAEP,EAAA,EAAA,KAAC,EAAD,CAAY,SAAUK,EAAc,MAAOL,EAAE,gBAAgB,CAAE,CAAA,GAG/D,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,uDAAf,EACE,EAAA,EAAA,KAAC,IAAD,CAAG,UAAU,6CACVA,EAAE,iBAAiB,CACnB,CAAA,GACH,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,+BAA+B,cAAY,sBACxDG,CACG,CAAA,CACH,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,+BAAf,EACE,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,+CACV,GAAG,kBAEFH,EAAE,iBAAiB,CAChB,CAAA,GACN,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,yDACV,YAAeO,OAAOC,QAAQC,KAAK,EACnC,KAAK,kBAHP,EAKE,EAAA,EAAA,KAAC,EAAD,CAAW,UAAU,SAAS,OAAO,MAAM,CAAA,EAC1CT,EAAE,cAAc,CACX,GACL,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,yDAAf,CACGA,EAAE,uBAAuB,EAAG,KAC7B,EAAA,EAAA,KAAC,IAAD,CACE,UAAU,6BACV,KAAK,sCAEJA,EAAE,6BAA6B,CAC/B,CAAA,CACA,GACK,GAEhB"}
@@ -0,0 +1,2 @@
1
+ import{N as e}from"./index-BmfaaNx6.js";var t=e;export{t as errorComponent};
2
+ //# sourceMappingURL=forgot-BMEIiDXv.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forgot-SSKfXB7c.js","names":["RouteErrorFallback","ForgotPasswordFormValues","email","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/password/forgot/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport { CheckCircleIcon, EnvelopeSimpleIcon } from '@phosphor-icons/react';\nimport { useMutation } from '@tanstack/react-query';\nimport { createFileRoute, Link, redirect } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { FooterLink } from '#frontend/components/auth/footer-link.tsx';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { forgotPasswordMutationOptions } from '#frontend/queries/password-reset.ts';\n\nexport const Route = createFileRoute('/password/forgot/')({\n component: ForgotPassword,\n errorComponent: RouteErrorFallback,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config?.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype ForgotPasswordFormValues = {\n email: string;\n};\n\nfunction ForgotPassword() {\n const { t } = useTranslation();\n const [emailSent, setEmailSent] = useState(false);\n const [submittedEmail, setSubmittedEmail] = useState('');\n\n const forgotPasswordSchema = useMemo(\n () =>\n z.object({\n email: z.email({ error: t('validation.email.invalid') }),\n }),\n [t],\n );\n\n const forgotPasswordMutation = useMutation({\n ...forgotPasswordMutationOptions,\n onSuccess: () => {\n setEmailSent(true);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<ForgotPasswordFormValues>({\n defaultValues: {\n email: '',\n },\n resolver: standardSchemaResolver(forgotPasswordSchema),\n });\n\n const onSubmit = async (values: ForgotPasswordFormValues) => {\n try {\n setSubmittedEmail(values.email);\n await forgotPasswordMutation.mutateAsync(values);\n } catch (error) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'USER_NOT_EDITABLE'\n ) {\n setError('email', {\n type: 'manual',\n message: t('forgotPassword.error.notEditable'),\n });\n } else {\n setEmailSent(true);\n }\n }\n };\n\n if (emailSent) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('forgotPassword.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('forgotPassword.success.description', {\n email: submittedEmail,\n })}\n title={t('forgotPassword.success.subtitle')}\n />\n\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n {t('forgotPassword.success.checkSpam')}\n </Alert>\n\n <Link\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n to=\"/login\"\n >\n {t('forgotPassword.backToLogin')}\n </Link>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('forgotPassword.subtitle')}\n title={t('forgotPassword.title')}\n />\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n autoComplete=\"email\"\n error={errors.email}\n icon={EnvelopeSimpleIcon}\n placeholder={t('forgotPassword.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={forgotPasswordMutation.isPending}\n pendingText={t('forgotPassword.submitting')}\n >\n {t('forgotPassword.submit')}\n </SubmitButton>\n </form>\n\n <FooterLink\n as={Link}\n linkText={t('register.link.login')}\n text={t('forgotPassword.footer.rememberedPassword')}\n to=\"/login\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"wCAqCE,IAAAG,EAxBOH"}
1
+ {"version":3,"file":"forgot-BMEIiDXv.js","names":["RouteErrorFallback","ForgotPasswordFormValues","email","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/password/forgot/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport { CheckCircleIcon, EnvelopeSimpleIcon } from '@phosphor-icons/react';\nimport { useMutation } from '@tanstack/react-query';\nimport { createFileRoute, Link, redirect } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { FooterLink } from '#frontend/components/auth/footer-link.tsx';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { forgotPasswordMutationOptions } from '#frontend/queries/password-reset.ts';\n\nexport const Route = createFileRoute('/password/forgot/')({\n component: ForgotPassword,\n errorComponent: RouteErrorFallback,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config?.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype ForgotPasswordFormValues = {\n email: string;\n};\n\nfunction ForgotPassword() {\n const { t } = useTranslation();\n const [emailSent, setEmailSent] = useState(false);\n const [submittedEmail, setSubmittedEmail] = useState('');\n\n const forgotPasswordSchema = useMemo(\n () =>\n z.object({\n email: z.email({ error: t('validation.email.invalid') }),\n }),\n [t],\n );\n\n const forgotPasswordMutation = useMutation({\n ...forgotPasswordMutationOptions,\n onSuccess: () => {\n setEmailSent(true);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<ForgotPasswordFormValues>({\n defaultValues: {\n email: '',\n },\n resolver: standardSchemaResolver(forgotPasswordSchema),\n });\n\n const onSubmit = async (values: ForgotPasswordFormValues) => {\n try {\n setSubmittedEmail(values.email);\n await forgotPasswordMutation.mutateAsync(values);\n } catch (error) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'USER_NOT_EDITABLE'\n ) {\n setError('email', {\n type: 'manual',\n message: t('forgotPassword.error.notEditable'),\n });\n } else {\n setEmailSent(true);\n }\n }\n };\n\n if (emailSent) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('forgotPassword.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('forgotPassword.success.description', {\n email: submittedEmail,\n })}\n title={t('forgotPassword.success.subtitle')}\n />\n\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n {t('forgotPassword.success.checkSpam')}\n </Alert>\n\n <Link\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n to=\"/login\"\n >\n {t('forgotPassword.backToLogin')}\n </Link>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('forgotPassword.subtitle')}\n title={t('forgotPassword.title')}\n />\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n autoComplete=\"email\"\n error={errors.email}\n icon={EnvelopeSimpleIcon}\n placeholder={t('forgotPassword.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={forgotPasswordMutation.isPending}\n pendingText={t('forgotPassword.submitting')}\n >\n {t('forgotPassword.submit')}\n </SubmitButton>\n </form>\n\n <FooterLink\n as={Link}\n linkText={t('register.link.login')}\n text={t('forgotPassword.footer.rememberedPassword')}\n to=\"/login\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"wCAqCE,IAAAG,EAxBOH"}
@@ -1,2 +1,2 @@
1
- import{o as e,r as t,s as n,u as r}from"./IconBase.es-d5KP98Ac.js";import{f as i}from"./use-theme-cVUDAjtt.js";import{t as a}from"./useMutation-DVMopbtG.js";import{t as o}from"./CheckCircle.es-MnJIACCe.js";import{t as s}from"./page-layout-C475gs09.js";import{t as c}from"./EnvelopeSimple.es-BZ7u3LYh.js";import{i as l,o as u}from"./zod-BItJDQBQ.js";import{t as d}from"./page-header-BYMFSGfT.js";import{t as f}from"./alert-CSXqgDVi.js";import{r as p,t as m}from"./standard-schema-o4V-s4uY.js";import{t as h}from"./footer-link-Ib1Hd-fr.js";import{t as g}from"./icon-input-8iU7PNzd.js";import{t as _}from"./submit-button-Xx6DwLyh.js";import{t as v}from"./password-reset-XZJTgJi3.js";var y=r(n()),b=e();function x(){let{t:e}=t(),[n,r]=(0,y.useState)(!1),[x,S]=(0,y.useState)(``),C=(0,y.useMemo)(()=>u({email:l({error:e(`validation.email.invalid`)})}),[e]),w=a({...v,onSuccess:()=>{r(!0)}}),{register:T,setError:E,handleSubmit:D,formState:{errors:O}}=p({defaultValues:{email:``},resolver:m(C)});return n?(0,b.jsxs)(s,{cardPadding:!0,maxWidth:`100`,children:[(0,b.jsx)(f,{className:`mb-4`,icon:o,type:`success`,children:e(`forgotPassword.success.title`)}),(0,b.jsx)(d,{subtitle:e(`forgotPassword.success.description`,{email:x}),title:e(`forgotPassword.success.subtitle`)}),(0,b.jsx)(f,{className:`mb-4`,icon:c,type:`info`,children:e(`forgotPassword.success.checkSpam`)}),(0,b.jsx)(i,{className:`btn btn-block h-10 font-semibold text-[14px]`,to:`/login`,children:e(`forgotPassword.backToLogin`)})]}):(0,b.jsxs)(s,{cardPadding:!0,maxWidth:`100`,children:[(0,b.jsx)(d,{subtitle:e(`forgotPassword.subtitle`),title:e(`forgotPassword.title`)}),(0,b.jsxs)(`form`,{className:`flex flex-col gap-4`,onSubmit:D(async t=>{try{S(t.email),await w.mutateAsync(t)}catch(t){t&&typeof t==`object`&&`code`in t&&t.code===`USER_NOT_EDITABLE`?E(`email`,{type:`manual`,message:e(`forgotPassword.error.notEditable`)}):r(!0)}}),children:[(0,b.jsx)(g,{autoComplete:`email`,error:O.email,icon:c,placeholder:e(`forgotPassword.email.placeholder`),...T(`email`),type:`email`}),(0,b.jsx)(_,{className:`mt-2`,isPending:w.isPending,pendingText:e(`forgotPassword.submitting`),children:e(`forgotPassword.submit`)})]}),(0,b.jsx)(h,{as:i,linkText:e(`register.link.login`),text:e(`forgotPassword.footer.rememberedPassword`),to:`/login`})]})}export{x as component};
2
- //# sourceMappingURL=forgot-x-UDyHXT.js.map
1
+ import{o as e,r as t,s as n,u as r}from"./IconBase.es-d5KP98Ac.js";import{f as i}from"./use-theme-cVUDAjtt.js";import{t as a}from"./useMutation-Iu4AJCB4.js";import{t as o}from"./CheckCircle.es-MnJIACCe.js";import{t as s}from"./page-layout-C475gs09.js";import{t as c}from"./EnvelopeSimple.es-BZ7u3LYh.js";import{i as l,o as u}from"./zod-BItJDQBQ.js";import{t as d}from"./page-header-BYMFSGfT.js";import{t as f}from"./alert-CSXqgDVi.js";import{r as p,t as m}from"./standard-schema-o4V-s4uY.js";import{t as h}from"./footer-link-Ib1Hd-fr.js";import{t as g}from"./icon-input-8iU7PNzd.js";import{t as _}from"./submit-button-Xx6DwLyh.js";import{t as v}from"./password-reset-XZJTgJi3.js";var y=r(n()),b=e();function x(){let{t:e}=t(),[n,r]=(0,y.useState)(!1),[x,S]=(0,y.useState)(``),C=(0,y.useMemo)(()=>u({email:l({error:e(`validation.email.invalid`)})}),[e]),w=a({...v,onSuccess:()=>{r(!0)}}),{register:T,setError:E,handleSubmit:D,formState:{errors:O}}=p({defaultValues:{email:``},resolver:m(C)});return n?(0,b.jsxs)(s,{cardPadding:!0,maxWidth:`100`,children:[(0,b.jsx)(f,{className:`mb-4`,icon:o,type:`success`,children:e(`forgotPassword.success.title`)}),(0,b.jsx)(d,{subtitle:e(`forgotPassword.success.description`,{email:x}),title:e(`forgotPassword.success.subtitle`)}),(0,b.jsx)(f,{className:`mb-4`,icon:c,type:`info`,children:e(`forgotPassword.success.checkSpam`)}),(0,b.jsx)(i,{className:`btn btn-block h-10 font-semibold text-[14px]`,to:`/login`,children:e(`forgotPassword.backToLogin`)})]}):(0,b.jsxs)(s,{cardPadding:!0,maxWidth:`100`,children:[(0,b.jsx)(d,{subtitle:e(`forgotPassword.subtitle`),title:e(`forgotPassword.title`)}),(0,b.jsxs)(`form`,{className:`flex flex-col gap-4`,onSubmit:D(async t=>{try{S(t.email),await w.mutateAsync(t)}catch(t){t&&typeof t==`object`&&`code`in t&&t.code===`USER_NOT_EDITABLE`?E(`email`,{type:`manual`,message:e(`forgotPassword.error.notEditable`)}):r(!0)}}),children:[(0,b.jsx)(g,{autoComplete:`email`,error:O.email,icon:c,placeholder:e(`forgotPassword.email.placeholder`),...T(`email`),type:`email`}),(0,b.jsx)(_,{className:`mt-2`,isPending:w.isPending,pendingText:e(`forgotPassword.submitting`),children:e(`forgotPassword.submit`)})]}),(0,b.jsx)(h,{as:i,linkText:e(`register.link.login`),text:e(`forgotPassword.footer.rememberedPassword`),to:`/login`})]})}export{x as component};
2
+ //# sourceMappingURL=forgot-C9zSfaX9.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forgot-x-UDyHXT.js","names":["standardSchemaResolver","CheckCircleIcon","EnvelopeSimpleIcon","useMutation","Link","useMemo","useState","useForm","useTranslation","z","FooterLink","IconInput","PageHeader","SubmitButton","Alert","PageLayout","forgotPasswordMutationOptions","ForgotPasswordFormValues","email","ForgotPassword","t","emailSent","setEmailSent","submittedEmail","setSubmittedEmail","forgotPasswordSchema","object","error","forgotPasswordMutation","onSuccess","register","setError","handleSubmit","formState","errors","defaultValues","resolver","onSubmit","values","mutateAsync","code","type","message","isPending","component"],"sources":["../../../frontend/src/routes/password/forgot/index.tsx?tsr-split=component"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport { CheckCircleIcon, EnvelopeSimpleIcon } from '@phosphor-icons/react';\nimport { useMutation } from '@tanstack/react-query';\nimport { createFileRoute, Link, redirect } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { FooterLink } from '#frontend/components/auth/footer-link.tsx';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { forgotPasswordMutationOptions } from '#frontend/queries/password-reset.ts';\n\nexport const Route = createFileRoute('/password/forgot/')({\n component: ForgotPassword,\n errorComponent: RouteErrorFallback,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config?.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype ForgotPasswordFormValues = {\n email: string;\n};\n\nfunction ForgotPassword() {\n const { t } = useTranslation();\n const [emailSent, setEmailSent] = useState(false);\n const [submittedEmail, setSubmittedEmail] = useState('');\n\n const forgotPasswordSchema = useMemo(\n () =>\n z.object({\n email: z.email({ error: t('validation.email.invalid') }),\n }),\n [t],\n );\n\n const forgotPasswordMutation = useMutation({\n ...forgotPasswordMutationOptions,\n onSuccess: () => {\n setEmailSent(true);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<ForgotPasswordFormValues>({\n defaultValues: {\n email: '',\n },\n resolver: standardSchemaResolver(forgotPasswordSchema),\n });\n\n const onSubmit = async (values: ForgotPasswordFormValues) => {\n try {\n setSubmittedEmail(values.email);\n await forgotPasswordMutation.mutateAsync(values);\n } catch (error) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'USER_NOT_EDITABLE'\n ) {\n setError('email', {\n type: 'manual',\n message: t('forgotPassword.error.notEditable'),\n });\n } else {\n setEmailSent(true);\n }\n }\n };\n\n if (emailSent) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('forgotPassword.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('forgotPassword.success.description', {\n email: submittedEmail,\n })}\n title={t('forgotPassword.success.subtitle')}\n />\n\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n {t('forgotPassword.success.checkSpam')}\n </Alert>\n\n <Link\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n to=\"/login\"\n >\n {t('forgotPassword.backToLogin')}\n </Link>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('forgotPassword.subtitle')}\n title={t('forgotPassword.title')}\n />\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n autoComplete=\"email\"\n error={errors.email}\n icon={EnvelopeSimpleIcon}\n placeholder={t('forgotPassword.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={forgotPasswordMutation.isPending}\n pendingText={t('forgotPassword.submitting')}\n >\n {t('forgotPassword.submit')}\n </SubmitButton>\n </form>\n\n <FooterLink\n as={Link}\n linkText={t('register.link.login')}\n text={t('forgotPassword.footer.rememberedPassword')}\n to=\"/login\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"2rBAuCA,SAASmB,GAAiB,CACxB,GAAM,CAAEC,KAAMZ,EAAe,EACvB,CAACa,EAAWC,IAAAA,EAAAA,EAAAA,UAAyB,EAAK,EAC1C,CAACC,EAAgBC,IAAAA,EAAAA,EAAAA,UAA8B,EAAE,EAEjDC,GAAAA,EAAAA,EAAAA,aAEFhB,EAAS,CACPS,MAAOT,EAAQ,CAAEkB,MAAOP,EAAE,0BAA0B,CAAE,CAAC,CACzD,CAAC,EACH,CAACA,CAAC,CACJ,EAEMQ,EAAyBzB,EAAY,CACzC,GAAGa,EACHa,cAAiB,CACfP,EAAa,EAAI,CACnB,CACF,CAAC,EAEK,CACJQ,WACAC,WACAC,eACAC,UAAW,CAAEC,WACX3B,EAAkC,CACpC4B,cAAe,CACbjB,MAAO,EACT,EACAkB,SAAUpC,EAAuByB,CAAoB,CACvD,CAAC,EAmDD,OA5BIJ,GAEA,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,OAAO,KAAMpB,EAAiB,KAAK,mBACjDmB,EAAE,8BAA8B,CAC5B,CAAA,GAEP,EAAA,EAAA,KAAC,EAAD,CACE,SAAUA,EAAE,qCAAsC,CAChDF,MAAOK,CACT,CAAC,EACD,MAAOH,EAAE,iCAAiC,CAAE,CAAA,GAG9C,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,OAAO,KAAMlB,EAAoB,KAAK,gBACpDkB,EAAE,kCAAkC,CAChC,CAAA,GAEP,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,+CACV,GAAG,kBAEFA,EAAE,4BAA4B,CAC3B,CAAA,CACI,KAKd,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CACE,SAAUA,EAAE,yBAAyB,EACrC,MAAOA,EAAE,sBAAsB,CAAE,CAAA,GAGnC,EAAA,EAAA,MAAC,OAAD,CAAM,UAAU,sBAAsB,SAAUY,EAAaK,KAxDzCC,IAAqC,CAC3D,GAAI,CACFd,EAAkBc,EAAOpB,KAAK,EAC9B,MAAMU,EAAuBW,YAAYD,CAAM,CACjD,OAASX,EAAO,CAEZA,GACA,OAAOA,GAAU,UACjB,SAAUA,GACVA,EAAMa,OAAS,oBAEfT,EAAS,QAAS,CAChBU,KAAM,SACNC,QAAStB,EAAE,kCAAkC,CAC/C,CAAC,EAEDE,EAAa,EAAI,CAErB,CACF,CAqCyE,WAArE,EACE,EAAA,EAAA,KAAC,EAAD,CACE,aAAa,QACb,MAAOY,EAAOhB,MACd,KAAMhB,EACN,YAAakB,EAAE,kCAAkC,EACjD,GAAIU,EAAS,OAAO,EACpB,KAAK,OAAO,CAAA,GAGd,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,OACV,UAAWF,EAAuBe,UAClC,YAAavB,EAAE,2BAA2B,WAEzCA,EAAE,uBAAuB,CACd,CAAA,CACV,KAEN,EAAA,EAAA,KAAC,EAAD,CACE,GAAIhB,EACJ,SAAUgB,EAAE,qBAAqB,EACjC,KAAMA,EAAE,0CAA0C,EAClD,GAAG,QAAQ,CAAA,CAEH,GAEhB"}
1
+ {"version":3,"file":"forgot-C9zSfaX9.js","names":["standardSchemaResolver","CheckCircleIcon","EnvelopeSimpleIcon","useMutation","Link","useMemo","useState","useForm","useTranslation","z","FooterLink","IconInput","PageHeader","SubmitButton","Alert","PageLayout","forgotPasswordMutationOptions","ForgotPasswordFormValues","email","ForgotPassword","t","emailSent","setEmailSent","submittedEmail","setSubmittedEmail","forgotPasswordSchema","object","error","forgotPasswordMutation","onSuccess","register","setError","handleSubmit","formState","errors","defaultValues","resolver","onSubmit","values","mutateAsync","code","type","message","isPending","component"],"sources":["../../../frontend/src/routes/password/forgot/index.tsx?tsr-split=component"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport { CheckCircleIcon, EnvelopeSimpleIcon } from '@phosphor-icons/react';\nimport { useMutation } from '@tanstack/react-query';\nimport { createFileRoute, Link, redirect } from '@tanstack/react-router';\nimport { useMemo, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { FooterLink } from '#frontend/components/auth/footer-link.tsx';\nimport { IconInput } from '#frontend/components/auth/icon-input.tsx';\nimport { PageHeader } from '#frontend/components/auth/page-header.tsx';\nimport { SubmitButton } from '#frontend/components/auth/submit-button.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { PageLayout } from '#frontend/features/layout/page-layout.tsx';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { forgotPasswordMutationOptions } from '#frontend/queries/password-reset.ts';\n\nexport const Route = createFileRoute('/password/forgot/')({\n component: ForgotPassword,\n errorComponent: RouteErrorFallback,\n beforeLoad: async ({ context }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n const isPasswordAuthEnabled = config?.auth.password.enabled;\n if (!isPasswordAuthEnabled) {\n throw redirect({ to: '/login' });\n }\n },\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n});\n\ntype ForgotPasswordFormValues = {\n email: string;\n};\n\nfunction ForgotPassword() {\n const { t } = useTranslation();\n const [emailSent, setEmailSent] = useState(false);\n const [submittedEmail, setSubmittedEmail] = useState('');\n\n const forgotPasswordSchema = useMemo(\n () =>\n z.object({\n email: z.email({ error: t('validation.email.invalid') }),\n }),\n [t],\n );\n\n const forgotPasswordMutation = useMutation({\n ...forgotPasswordMutationOptions,\n onSuccess: () => {\n setEmailSent(true);\n },\n });\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm<ForgotPasswordFormValues>({\n defaultValues: {\n email: '',\n },\n resolver: standardSchemaResolver(forgotPasswordSchema),\n });\n\n const onSubmit = async (values: ForgotPasswordFormValues) => {\n try {\n setSubmittedEmail(values.email);\n await forgotPasswordMutation.mutateAsync(values);\n } catch (error) {\n if (\n error &&\n typeof error === 'object' &&\n 'code' in error &&\n error.code === 'USER_NOT_EDITABLE'\n ) {\n setError('email', {\n type: 'manual',\n message: t('forgotPassword.error.notEditable'),\n });\n } else {\n setEmailSent(true);\n }\n }\n };\n\n if (emailSent) {\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <Alert className=\"mb-4\" icon={CheckCircleIcon} type=\"success\">\n {t('forgotPassword.success.title')}\n </Alert>\n\n <PageHeader\n subtitle={t('forgotPassword.success.description', {\n email: submittedEmail,\n })}\n title={t('forgotPassword.success.subtitle')}\n />\n\n <Alert className=\"mb-4\" icon={EnvelopeSimpleIcon} type=\"info\">\n {t('forgotPassword.success.checkSpam')}\n </Alert>\n\n <Link\n className=\"btn btn-block h-10 font-semibold text-[14px]\"\n to=\"/login\"\n >\n {t('forgotPassword.backToLogin')}\n </Link>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout cardPadding maxWidth=\"100\">\n <PageHeader\n subtitle={t('forgotPassword.subtitle')}\n title={t('forgotPassword.title')}\n />\n\n <form className=\"flex flex-col gap-4\" onSubmit={handleSubmit(onSubmit)}>\n <IconInput\n autoComplete=\"email\"\n error={errors.email}\n icon={EnvelopeSimpleIcon}\n placeholder={t('forgotPassword.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <SubmitButton\n className=\"mt-2\"\n isPending={forgotPasswordMutation.isPending}\n pendingText={t('forgotPassword.submitting')}\n >\n {t('forgotPassword.submit')}\n </SubmitButton>\n </form>\n\n <FooterLink\n as={Link}\n linkText={t('register.link.login')}\n text={t('forgotPassword.footer.rememberedPassword')}\n to=\"/login\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"2rBAuCA,SAASmB,GAAiB,CACxB,GAAM,CAAEC,KAAMZ,EAAe,EACvB,CAACa,EAAWC,IAAAA,EAAAA,EAAAA,UAAyB,EAAK,EAC1C,CAACC,EAAgBC,IAAAA,EAAAA,EAAAA,UAA8B,EAAE,EAEjDC,GAAAA,EAAAA,EAAAA,aAEFhB,EAAS,CACPS,MAAOT,EAAQ,CAAEkB,MAAOP,EAAE,0BAA0B,CAAE,CAAC,CACzD,CAAC,EACH,CAACA,CAAC,CACJ,EAEMQ,EAAyBzB,EAAY,CACzC,GAAGa,EACHa,cAAiB,CACfP,EAAa,EAAI,CACnB,CACF,CAAC,EAEK,CACJQ,WACAC,WACAC,eACAC,UAAW,CAAEC,WACX3B,EAAkC,CACpC4B,cAAe,CACbjB,MAAO,EACT,EACAkB,SAAUpC,EAAuByB,CAAoB,CACvD,CAAC,EAmDD,OA5BIJ,GAEA,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,OAAO,KAAMpB,EAAiB,KAAK,mBACjDmB,EAAE,8BAA8B,CAC5B,CAAA,GAEP,EAAA,EAAA,KAAC,EAAD,CACE,SAAUA,EAAE,qCAAsC,CAChDF,MAAOK,CACT,CAAC,EACD,MAAOH,EAAE,iCAAiC,CAAE,CAAA,GAG9C,EAAA,EAAA,KAAC,EAAD,CAAO,UAAU,OAAO,KAAMlB,EAAoB,KAAK,gBACpDkB,EAAE,kCAAkC,CAChC,CAAA,GAEP,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,+CACV,GAAG,kBAEFA,EAAE,4BAA4B,CAC3B,CAAA,CACI,KAKd,EAAA,EAAA,MAAC,EAAD,CAAY,YAAA,GAAY,SAAS,eAAjC,EACE,EAAA,EAAA,KAAC,EAAD,CACE,SAAUA,EAAE,yBAAyB,EACrC,MAAOA,EAAE,sBAAsB,CAAE,CAAA,GAGnC,EAAA,EAAA,MAAC,OAAD,CAAM,UAAU,sBAAsB,SAAUY,EAAaK,KAxDzCC,IAAqC,CAC3D,GAAI,CACFd,EAAkBc,EAAOpB,KAAK,EAC9B,MAAMU,EAAuBW,YAAYD,CAAM,CACjD,OAASX,EAAO,CAEZA,GACA,OAAOA,GAAU,UACjB,SAAUA,GACVA,EAAMa,OAAS,oBAEfT,EAAS,QAAS,CAChBU,KAAM,SACNC,QAAStB,EAAE,kCAAkC,CAC/C,CAAC,EAEDE,EAAa,EAAI,CAErB,CACF,CAqCyE,WAArE,EACE,EAAA,EAAA,KAAC,EAAD,CACE,aAAa,QACb,MAAOY,EAAOhB,MACd,KAAMhB,EACN,YAAakB,EAAE,kCAAkC,EACjD,GAAIU,EAAS,OAAO,EACpB,KAAK,OAAO,CAAA,GAGd,EAAA,EAAA,KAAC,EAAD,CACE,UAAU,OACV,UAAWF,EAAuBe,UAClC,YAAavB,EAAE,2BAA2B,WAEzCA,EAAE,uBAAuB,CACd,CAAA,CACV,KAEN,EAAA,EAAA,KAAC,EAAD,CACE,GAAIhB,EACJ,SAAUgB,EAAE,qBAAqB,EACjC,KAAMA,EAAE,0CAA0C,EAClD,GAAG,QAAQ,CAAA,CAEH,GAEhB"}