@tinyrack/issuary-server 0.23.0 → 0.25.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.
- package/dist/entrypoints/app.d.ts +0 -6
- package/dist/entrypoints/app.d.ts.map +1 -1
- package/dist/lib/config/branding.d.ts +0 -4
- package/dist/lib/config/branding.d.ts.map +1 -1
- package/dist/lib/config/branding.js +0 -14
- package/dist/lib/config/branding.js.map +1 -1
- package/dist/lib/config/resolved.d.ts +0 -2
- package/dist/lib/config/resolved.d.ts.map +1 -1
- package/dist/routes/api/config/get.d.ts +0 -4
- package/dist/routes/api/config/get.d.ts.map +1 -1
- package/dist/routes/api/config/get.js +0 -2
- package/dist/routes/api/config/get.js.map +1 -1
- package/dist/routes/api/config/index.d.ts +0 -4
- package/dist/routes/api/config/index.d.ts.map +1 -1
- package/dist/routes/api/index.d.ts +0 -4
- package/dist/routes/api/index.d.ts.map +1 -1
- package/dist/routes/index.d.ts +0 -4
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/schemas/response.d.ts +0 -2
- package/dist/schemas/response.d.ts.map +1 -1
- package/dist/schemas/response.js +0 -5
- package/dist/schemas/response.js.map +1 -1
- package/dist/services/container.d.ts +0 -2
- package/dist/services/container.d.ts.map +1 -1
- package/package.json +2 -1
- package/public/assets/2fa-BlpDkf-r.js +2 -0
- package/public/assets/{2fa-CCOTIHDZ.js.map → 2fa-BlpDkf-r.js.map} +1 -1
- package/public/assets/{2fa-B5vLgDXQ.js → 2fa-DIpGlpoI.js} +2 -2
- package/public/assets/{2fa-B5vLgDXQ.js.map → 2fa-DIpGlpoI.js.map} +1 -1
- package/public/assets/2fa-tzTSqiOS.js +2 -0
- package/public/assets/{2fa-C5vUVgr_.js.map → 2fa-tzTSqiOS.js.map} +1 -1
- package/public/assets/{2fa-BqsM_D-X.js → 2fa-waFAdIa0.js} +2 -2
- package/public/assets/{2fa-BqsM_D-X.js.map → 2fa-waFAdIa0.js.map} +1 -1
- package/public/assets/DialogHandle-ChG5Sj7T.js +2 -0
- package/public/assets/{DialogHandle-BmtK2pRn.js.map → DialogHandle-ChG5Sj7T.js.map} +1 -1
- package/public/assets/admin-Ii29X08B.js +2 -0
- package/public/assets/admin-Ii29X08B.js.map +1 -0
- package/public/assets/{admin-users-DvPbw1Vq.js → admin-users-d7tT-cjc.js} +2 -2
- package/public/assets/{admin-users-DvPbw1Vq.js.map → admin-users-d7tT-cjc.js.map} +1 -1
- package/public/assets/alert-leqyvHOp.js.map +1 -1
- package/public/assets/{auth-choice-row-BCGjuedo.js → auth-choice-row-DUvFyo7b.js} +2 -2
- package/public/assets/{auth-choice-row-BCGjuedo.js.map → auth-choice-row-DUvFyo7b.js.map} +1 -1
- package/public/assets/{auth-field-DD8kvl--.js → auth-field-BSlAWkbx.js} +2 -2
- package/public/assets/{auth-field-DD8kvl--.js.map → auth-field-BSlAWkbx.js.map} +1 -1
- package/public/assets/auth-layout-CcJy6dVY.js +2 -0
- package/public/assets/auth-layout-CcJy6dVY.js.map +1 -0
- package/public/assets/{auth-page-header-CIn2E4zI.js → auth-page-header-ZLhtQ7Et.js} +2 -2
- package/public/assets/auth-page-header-ZLhtQ7Et.js.map +1 -0
- package/public/assets/authorization-context-banner-DfkjwE93.js +2 -0
- package/public/assets/authorization-context-banner-DfkjwE93.js.map +1 -0
- package/public/assets/{avatar-BEQjf2x6.js → avatar-D-70MKp8.js} +2 -2
- package/public/assets/{avatar-BEQjf2x6.js.map → avatar-D-70MKp8.js.map} +1 -1
- package/public/assets/badge-CsOfUji0.js.map +1 -1
- package/public/assets/checkbox-C3UmXoKz.js +2 -0
- package/public/assets/{checkbox-gdHnSFQ-.js.map → checkbox-C3UmXoKz.js.map} +1 -1
- package/public/assets/config-DTQCUlfZ.js.map +1 -1
- package/public/assets/{consent-CfRbz2R7.js → consent-BJgO9pEV.js} +2 -2
- package/public/assets/{consent-CfRbz2R7.js.map → consent-BJgO9pEV.js.map} +1 -1
- package/public/assets/{consent-BqqFDdsr.js → consent-D_yGE8TR.js} +2 -2
- package/public/assets/{consent-BqqFDdsr.js.map → consent-D_yGE8TR.js.map} +1 -1
- package/public/assets/createLucideIcon-BPpAIXR_.js.map +1 -1
- package/public/assets/{email-CKKakxPJ.js → email-DAa8JGoC.js} +2 -2
- package/public/assets/{email-CKKakxPJ.js.map → email-DAa8JGoC.js.map} +1 -1
- package/public/assets/email-tzTSqiOS.js +2 -0
- package/public/assets/{email-C5vUVgr_.js.map → email-tzTSqiOS.js.map} +1 -1
- package/public/assets/{error-BMXxpgfR.js → error-Dj4QQ8vW.js} +2 -2
- package/public/assets/{error-BMXxpgfR.js.map → error-Dj4QQ8vW.js.map} +1 -1
- package/public/assets/field-CEs4ugVJ.js +3 -0
- package/public/assets/{field-Cn4H1CUj.js.map → field-CEs4ugVJ.js.map} +1 -1
- package/public/assets/{forgot-rO9sByfV.js → forgot-CYMAWPVu.js} +2 -2
- package/public/assets/{forgot-rO9sByfV.js.map → forgot-CYMAWPVu.js.map} +1 -1
- package/public/assets/forgot-tzTSqiOS.js +2 -0
- package/public/assets/{forgot-C5vUVgr_.js.map → forgot-tzTSqiOS.js.map} +1 -1
- package/public/assets/{index-CS23Ozt6.js → index-H6CJKjiN.js} +3 -3
- package/public/assets/{index-CS23Ozt6.js.map → index-H6CJKjiN.js.map} +1 -1
- package/public/assets/{index-DZyHRhbQ.css → index-kD9gQA-w.css} +1 -1
- package/public/assets/{initial-avatar-CtuDUz_W.js → initial-avatar-DjSqgENY.js} +2 -2
- package/public/assets/{initial-avatar-CtuDUz_W.js.map → initial-avatar-DjSqgENY.js.map} +1 -1
- package/public/assets/{input-CzQki7k6.js → input-BHPUSlfp.js} +2 -2
- package/public/assets/{input-CzQki7k6.js.map → input-BHPUSlfp.js.map} +1 -1
- package/public/assets/link-C-WdTj3F.js.map +1 -1
- package/public/assets/{login-nukebli4.js → login-CBinfk4L.js} +2 -2
- package/public/assets/{login-nukebli4.js.map → login-CBinfk4L.js.map} +1 -1
- package/public/assets/login-tzTSqiOS.js +2 -0
- package/public/assets/{login-C5vUVgr_.js.map → login-tzTSqiOS.js.map} +1 -1
- package/public/assets/{modal-BDQFucjz.js → modal-CwoBUXp3.js} +2 -2
- package/public/assets/{modal-BDQFucjz.js.map → modal-CwoBUXp3.js.map} +1 -1
- package/public/assets/{passkey-CynCz1Dt.js → passkey-Jw2CRzJp.js} +2 -2
- package/public/assets/{passkey-CynCz1Dt.js.map → passkey-Jw2CRzJp.js.map} +1 -1
- package/public/assets/{passkey-BE9JvJOP.js → passkey-V_tav53p.js} +2 -2
- package/public/assets/{passkey-BE9JvJOP.js.map → passkey-V_tav53p.js.map} +1 -1
- package/public/assets/passkey-tzTSqiOS.js +2 -0
- package/public/assets/{passkey-C5vUVgr_.js.map → passkey-tzTSqiOS.js.map} +1 -1
- package/public/assets/{password-RkUttbhD.js → password-6QOqTV_s.js} +2 -2
- package/public/assets/{password-RkUttbhD.js.map → password-6QOqTV_s.js.map} +1 -1
- package/public/assets/{password-strength-BeSOu_Ga.js → password-strength-DtGMHOD-.js} +2 -2
- package/public/assets/{password-strength-BeSOu_Ga.js.map → password-strength-DtGMHOD-.js.map} +1 -1
- package/public/assets/password-tzTSqiOS.js +2 -0
- package/public/assets/{password-C5vUVgr_.js.map → password-tzTSqiOS.js.map} +1 -1
- package/public/assets/pin-input-DdhzDNy9.js +2 -0
- package/public/assets/{pin-input-Cc-VkrQ3.js.map → pin-input-DdhzDNy9.js.map} +1 -1
- package/public/assets/{profile-B5NaJfeS.js → profile-DVjIWt8X.js} +2 -2
- package/public/assets/{profile-B5NaJfeS.js.map → profile-DVjIWt8X.js.map} +1 -1
- package/public/assets/profile-SHr6F-7b.js +2 -0
- package/public/assets/profile-SHr6F-7b.js.map +1 -0
- package/public/assets/provider-logos-u1YURWQr.js.map +1 -1
- package/public/assets/{recovery-PnUATiUi.js → recovery-CvNl-yt9.js} +2 -2
- package/public/assets/{recovery-PnUATiUi.js.map → recovery-CvNl-yt9.js.map} +1 -1
- package/public/assets/{register-DQXhru0_.js → register-SFXa5EeL.js} +2 -2
- package/public/assets/{register-DQXhru0_.js.map → register-SFXa5EeL.js.map} +1 -1
- package/public/assets/register-tzTSqiOS.js +2 -0
- package/public/assets/{register-C5vUVgr_.js.map → register-tzTSqiOS.js.map} +1 -1
- package/public/assets/{reset-CQgIa3yf.js → reset-l-pLzjwe.js} +2 -2
- package/public/assets/{reset-CQgIa3yf.js.map → reset-l-pLzjwe.js.map} +1 -1
- package/public/assets/reset-tzTSqiOS.js +2 -0
- package/public/assets/{reset-C5vUVgr_.js.map → reset-tzTSqiOS.js.map} +1 -1
- package/public/assets/{select-DqZynXSt.js → select-D2H87qXJ.js} +2 -2
- package/public/assets/{select-DqZynXSt.js.map → select-D2H87qXJ.js.map} +1 -1
- package/public/assets/select-tzTSqiOS.js +2 -0
- package/public/assets/{select-C5vUVgr_.js.map → select-tzTSqiOS.js.map} +1 -1
- package/public/assets/separator-B0Bvh1he.js.map +1 -1
- package/public/assets/shell-header-bar-BPgCjFPv.js +2 -0
- package/public/assets/shell-header-bar-BPgCjFPv.js.map +1 -0
- package/public/assets/{terms-BHDAgn-H.js → terms-D5rfQhog.js} +2 -2
- package/public/assets/{terms-BHDAgn-H.js.map → terms-D5rfQhog.js.map} +1 -1
- package/public/assets/{terms-B5NaJfeS.js → terms-DVjIWt8X.js} +2 -2
- package/public/assets/{terms-B5NaJfeS.js.map → terms-DVjIWt8X.js.map} +1 -1
- package/public/assets/{terms-checkbox-list-6jteUTIZ.js → terms-checkbox-list-BKJcS2pJ.js} +2 -2
- package/public/assets/{terms-checkbox-list-6jteUTIZ.js.map → terms-checkbox-list-BKJcS2pJ.js.map} +1 -1
- package/public/assets/{totp-ECvOUcFx.js → totp-BbQi-gsF.js} +2 -2
- package/public/assets/{totp-ECvOUcFx.js.map → totp-BbQi-gsF.js.map} +1 -1
- package/public/assets/{totp-CJtfVNEq.js → totp-wY0Zji8E.js} +2 -2
- package/public/assets/{totp-CJtfVNEq.js.map → totp-wY0Zji8E.js.map} +1 -1
- package/public/assets/{use-totp-setup-BbS3XcZA.js → use-totp-setup-OH_GTMxA.js} +3 -3
- package/public/assets/{use-totp-setup-BbS3XcZA.js.map → use-totp-setup-OH_GTMxA.js.map} +1 -1
- package/public/assets/{useAriaLabelledBy-ByuMi3Ry.js → useAriaLabelledBy-9o83pv9X.js} +2 -2
- package/public/assets/{useAriaLabelledBy-ByuMi3Ry.js.map → useAriaLabelledBy-9o83pv9X.js.map} +1 -1
- package/public/assets/useQuery-CI5Ukuae.js +2 -0
- package/public/assets/{useQuery-Ckxjmvtl.js.map → useQuery-CI5Ukuae.js.map} +1 -1
- package/public/assets/users-DqCnblEo.js +2 -0
- package/public/assets/users-DqCnblEo.js.map +1 -0
- package/public/index.html +2 -2
- package/public/assets/2fa-C5vUVgr_.js +0 -2
- package/public/assets/2fa-CCOTIHDZ.js +0 -2
- package/public/assets/DialogHandle-BmtK2pRn.js +0 -2
- package/public/assets/admin-BPijkLw-.js +0 -2
- package/public/assets/admin-BPijkLw-.js.map +0 -1
- package/public/assets/auth-layout-VtpryDof.js +0 -2
- package/public/assets/auth-layout-VtpryDof.js.map +0 -1
- package/public/assets/auth-page-header-CIn2E4zI.js.map +0 -1
- package/public/assets/authorization-context-banner-SOuBHWIb.js +0 -2
- package/public/assets/authorization-context-banner-SOuBHWIb.js.map +0 -1
- package/public/assets/checkbox-gdHnSFQ-.js +0 -2
- package/public/assets/email-C5vUVgr_.js +0 -2
- package/public/assets/field-Cn4H1CUj.js +0 -3
- package/public/assets/forgot-C5vUVgr_.js +0 -2
- package/public/assets/login-C5vUVgr_.js +0 -2
- package/public/assets/passkey-C5vUVgr_.js +0 -2
- package/public/assets/password-C5vUVgr_.js +0 -2
- package/public/assets/pin-input-Cc-VkrQ3.js +0 -2
- package/public/assets/profile-BcjsVYOx.js +0 -2
- package/public/assets/profile-BcjsVYOx.js.map +0 -1
- package/public/assets/register-C5vUVgr_.js +0 -2
- package/public/assets/reset-C5vUVgr_.js +0 -2
- package/public/assets/select-C5vUVgr_.js +0 -2
- package/public/assets/shell-header-bar-CjG5qvPg.js +0 -2
- package/public/assets/shell-header-bar-CjG5qvPg.js.map +0 -1
- package/public/assets/useQuery-Ckxjmvtl.js +0 -2
- package/public/assets/users-CmFxrCQT.js +0 -2
- package/public/assets/users-CmFxrCQT.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{q as e}from"./createLucideIcon-BPpAIXR_.js";import{t}from"./avatar-
|
|
2
|
-
//# sourceMappingURL=initial-avatar-
|
|
1
|
+
import{q as e}from"./createLucideIcon-BPpAIXR_.js";import{t}from"./avatar-D-70MKp8.js";var n=e(),r={sm:{"--tr-avatar-size":`2rem`,"--tr-avatar-font-size":`var(--tinyrack-text-sm)`},md:{"--tr-avatar-size":`3rem`,"--tr-avatar-font-size":`var(--tinyrack-text-xl)`},lg:{"--tr-avatar-size":`4rem`,"--tr-avatar-font-size":`var(--tinyrack-text-2xl)`}};function i({email:e,size:i=`md`}){let a=e.charAt(0).toUpperCase();return(0,n.jsx)(t.Root,{className:`shrink-0 font-tinyrack-bold`,shape:`circle`,style:{...r[i],"--tr-avatar-background":`var(--tinyrack-primary)`,"--tr-avatar-color":`var(--tinyrack-on-primary)`,"--tr-avatar-border":`transparent`},children:(0,n.jsx)(t.Fallback,{children:a})})}export{i as t};
|
|
2
|
+
//# sourceMappingURL=initial-avatar-DjSqgENY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initial-avatar-
|
|
1
|
+
{"version":3,"file":"initial-avatar-DjSqgENY.js","names":[],"sources":["../../../frontend/src/components/ui/initial-avatar.tsx"],"sourcesContent":["import { TRAvatar } from '@tinyrack/ui/components/avatar';\nimport type { CSSProperties } from 'react';\n\ntype InitialAvatarProps = {\n email: string;\n size?: 'sm' | 'md' | 'lg';\n};\n\n/**\n * Brand-colored initial avatar built on the design system's `TRAvatar`.\n * Size, font size and the primary color pair are applied through the\n * component's own `--tr-avatar-*` custom properties so it composes the\n * design system rather than re-implementing an avatar.\n */\nconst sizeVars: Record<\n NonNullable<InitialAvatarProps['size']>,\n CSSProperties\n> = {\n sm: {\n '--tr-avatar-size': '2rem',\n '--tr-avatar-font-size': 'var(--tinyrack-text-sm)',\n } as CSSProperties,\n md: {\n '--tr-avatar-size': '3rem',\n '--tr-avatar-font-size': 'var(--tinyrack-text-xl)',\n } as CSSProperties,\n lg: {\n '--tr-avatar-size': '4rem',\n '--tr-avatar-font-size': 'var(--tinyrack-text-2xl)',\n } as CSSProperties,\n};\n\nexport function InitialAvatar({ email, size = 'md' }: InitialAvatarProps) {\n const initial = email.charAt(0).toUpperCase();\n\n return (\n <TRAvatar.Root\n className=\"shrink-0 font-tinyrack-bold\"\n shape=\"circle\"\n style={\n {\n ...sizeVars[size],\n '--tr-avatar-background': 'var(--tinyrack-primary)',\n '--tr-avatar-color': 'var(--tinyrack-on-primary)',\n '--tr-avatar-border': 'transparent',\n } as CSSProperties\n }\n >\n <TRAvatar.Fallback>{initial}</TRAvatar.Fallback>\n </TRAvatar.Root>\n );\n}\n"],"mappings":"iGAcM,EAGF,CACF,GAAI,CACF,mBAAoB,OACpB,wBAAyB,yBAC3B,EACA,GAAI,CACF,mBAAoB,OACpB,wBAAyB,yBAC3B,EACA,GAAI,CACF,mBAAoB,OACpB,wBAAyB,0BAC3B,CACF,EAEA,SAAgB,EAAc,CAAE,QAAO,OAAO,MAA4B,CACxE,IAAM,EAAU,EAAM,OAAO,CAAC,CAAC,CAAC,YAAY,EAE5C,OACE,EAAA,EAAA,IAAA,CAAC,EAAS,KAAV,CACE,UAAU,8BACV,MAAM,SACN,MACE,CACE,GAAG,EAAS,GACZ,yBAA0B,0BAC1B,oBAAqB,6BACrB,qBAAsB,aACxB,YAGF,EAAA,EAAA,IAAA,CAAC,EAAS,SAAV,CAAA,SAAoB,CAA2B,CAAA,CAClC,CAAA,CAEnB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{G as e,J as t,W as n,Z as r,q as i}from"./createLucideIcon-BPpAIXR_.js";import{xn as a}from"./config-DTQCUlfZ.js";import{n as o}from"./field-
|
|
2
|
-
//# sourceMappingURL=input-
|
|
1
|
+
import{G as e,J as t,W as n,Z as r,q as i}from"./createLucideIcon-BPpAIXR_.js";import{xn as a}from"./config-DTQCUlfZ.js";import{n as o}from"./field-CEs4ugVJ.js";var s=i(),c=r(t(),1),l=(0,c.createContext)(!1);function u(){return(0,c.useContext)(l)}var d=c.forwardRef(function(e,t){return(0,s.jsx)(o,{ref:t,...e})});function f({className:t,uiSize:n=`md`,appearance:r=`solid`,...i}){a();let o=u();return(0,s.jsx)(d,{...i,className:e(o?`tr-input tr-input-group-input`:`tr-input`,t),"data-appearance":r,"data-ui-size":n})}function p({className:e,type:t=`button`,...r}){return(0,s.jsx)(`button`,{...r,className:n(`tr-input-group-action`,e),type:t})}function m({className:e,side:t=`start`,...r}){return(0,s.jsx)(`span`,{...r,className:n(`tr-input-group-adornment`,e),"data-side":t})}function h({children:e,className:t,uiSize:r=`md`,...i}){return a(),(0,s.jsx)(`div`,{...i,className:n(`tr-input-group`,t),"data-ui-size":r,children:(0,s.jsx)(l,{value:!0,children:e})})}var g=Object.assign(f,{Group:h,Adornment:m,Action:p});export{g as t};
|
|
2
|
+
//# sourceMappingURL=input-BHPUSlfp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-
|
|
1
|
+
{"version":3,"file":"input-BHPUSlfp.js","names":["Field.Control","TRInput"],"sources":["../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/input/input-group-context.js","../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/input/Input.mjs","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/input/input.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/input/input-action.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/input/input-adornment.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/input/input-group.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/input/index.js"],"sourcesContent":["\"use client\";\nimport { createContext, useContext } from \"react\";\n//#region src/components/input/input-group-context.ts\n/**\n* Lets an `Input` discover that it is rendered inside a `Group` so it can take\n* the group's control styling itself. Without it a caller would have to write\n* the internal `tr-input-group-input` class by hand, which is the thing the\n* group is meant to spare them.\n*/\nconst TRInputGroupContext = createContext(false);\nfunction useIsInsideTRInputGroup() {\n\treturn useContext(TRInputGroupContext);\n}\n//#endregion\nexport { TRInputGroupContext, useIsInsideTRInputGroup };\n\n//# sourceMappingURL=input-group-context.js.map","'use client';\n\nimport * as React from 'react';\nimport { Field } from \"../field/index.mjs\";\n\n/**\n * A native input element that automatically works with [Field](https://base-ui.com/react/components/field).\n * Renders an `<input>` element.\n *\n * Documentation: [Base UI Input](https://base-ui.com/react/components/input)\n */\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport const Input = /*#__PURE__*/React.forwardRef(function Input(props, forwardedRef) {\n return /*#__PURE__*/_jsx(Field.Control, {\n ref: forwardedRef,\n ...props\n });\n});\nif (process.env.NODE_ENV !== \"production\") Input.displayName = \"Input\";","\"use client\";\nimport { useTinyrackFocusModality } from \"../../providers/focus-modality/focus-modality-provider.js\";\nimport { mergeComponentClassName } from \"../../internal/component-class-name.js\";\nimport { useIsInsideTRInputGroup } from \"./input-group-context.js\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Input } from \"@base-ui/react/input\";\n//#region src/components/input/input.tsx\nfunction TRInput({ className, uiSize = \"md\", appearance = \"solid\", ...props }) {\n\tuseTinyrackFocusModality();\n\tconst insideGroup = useIsInsideTRInputGroup();\n\treturn /* @__PURE__ */ jsx(Input, {\n\t\t...props,\n\t\tclassName: mergeComponentClassName(insideGroup ? \"tr-input tr-input-group-input\" : \"tr-input\", className),\n\t\t\"data-appearance\": appearance,\n\t\t\"data-ui-size\": uiSize\n\t});\n}\n//#endregion\nexport { TRInput };\n\n//# sourceMappingURL=input.js.map","import { mergeClassNames } from \"../../internal/component-class-name.js\";\nimport { jsx } from \"react/jsx-runtime\";\n//#region src/components/input/input-action.tsx\n/**\n* A control at the edge of a `Group` — reveal a password, clear the value, run\n* a search.\n*\n* Defaults to `type=\"button\"` so it cannot accidentally submit the surrounding\n* form. It is a real button and stays in the tab order, so give it an\n* accessible name.\n*/\nfunction TRInputAction({ className, type = \"button\", ...props }) {\n\treturn /* @__PURE__ */ jsx(\"button\", {\n\t\t...props,\n\t\tclassName: mergeClassNames(\"tr-input-group-action\", className),\n\t\ttype\n\t});\n}\n//#endregion\nexport { TRInputAction };\n\n//# sourceMappingURL=input-action.js.map","import { mergeClassNames } from \"../../internal/component-class-name.js\";\nimport { jsx } from \"react/jsx-runtime\";\n//#region src/components/input/input-adornment.tsx\n/**\n* Static content inside a `Group` — a leading icon, a unit, a prefix.\n*\n* Not interactive: it does not take pointer events, so clicking it still\n* focuses the input. Use `Action` for something the user can press, and mark\n* decorative icons `aria-hidden` so the field keeps a single accessible name.\n*/\nfunction TRInputAdornment({ className, side = \"start\", ...props }) {\n\treturn /* @__PURE__ */ jsx(\"span\", {\n\t\t...props,\n\t\tclassName: mergeClassNames(\"tr-input-group-adornment\", className),\n\t\t\"data-side\": side\n\t});\n}\n//#endregion\nexport { TRInputAdornment };\n\n//# sourceMappingURL=input-adornment.js.map","\"use client\";\nimport { useTinyrackFocusModality } from \"../../providers/focus-modality/focus-modality-provider.js\";\nimport { mergeClassNames } from \"../../internal/component-class-name.js\";\nimport { TRInputGroupContext } from \"./input-group-context.js\";\nimport { jsx } from \"react/jsx-runtime\";\n//#region src/components/input/input-group.tsx\n/**\n* Frames an input together with adornments and actions so they read as one\n* control: the border, background, and focus ring belong to the group, and the\n* input inside it goes flat.\n*\n* The group owns the control height, so size it here rather than on the input.\n*/\nfunction TRInputGroup({ children, className, uiSize = \"md\", ...props }) {\n\tuseTinyrackFocusModality();\n\treturn /* @__PURE__ */ jsx(\"div\", {\n\t\t...props,\n\t\tclassName: mergeClassNames(\"tr-input-group\", className),\n\t\t\"data-ui-size\": uiSize,\n\t\tchildren: /* @__PURE__ */ jsx(TRInputGroupContext, {\n\t\t\tvalue: true,\n\t\t\tchildren\n\t\t})\n\t});\n}\n//#endregion\nexport { TRInputGroup };\n\n//# sourceMappingURL=input-group.js.map","import { TRInput as TRInput$1 } from \"./input.js\";\nimport { TRInputAction } from \"./input-action.js\";\nimport { TRInputAdornment } from \"./input-adornment.js\";\nimport { TRInputGroup } from \"./input-group.js\";\n//#region src/components/input/index.tsx\n/**\n* `TRInput` stays directly renderable for the common single-control case and\n* carries the group parts as a namespace, so adding an adornment never means\n* changing the element you already render.\n*/\nconst TRInput = Object.assign(TRInput$1, {\n\tGroup: TRInputGroup,\n\tAdornment: TRInputAdornment,\n\tAction: TRInputAction\n});\n//#endregion\nexport { TRInput, TRInputAction, TRInputAdornment, TRInputGroup };\n\n//# sourceMappingURL=index.js.map"],"x_google_ignoreList":[0,1,2,3,4,5,6],"mappings":"sLASA,GAAA,EAAA,EAAA,cAAA,CAAA,EAAA,EACA,SAAA,GAAA,0BAEA,CCAA,IAAa,EAAqB,EAAM,WAAW,SAAe,EAAO,EAAc,CACrF,OAAoB,EAAA,EAAA,IAAA,CAAKA,EAAe,CACtC,IAAK,EACL,GAAG,CACL,CAAC,CACH,CAAC,ECVD,SAASC,EAAQ,CAAE,YAAW,SAAS,KAAM,aAAa,QAAS,GAAG,GAAS,CAC9E,EAAyB,EACzB,IAAM,EAAc,EAAwB,EAC5C,OAAuB,EAAA,EAAA,IAAA,CAAI,EAAO,CACjC,GAAG,EACH,UAAW,EAAwB,EAAc,gCAAkC,WAAY,CAAS,EACxG,kBAAmB,EACnB,eAAgB,CACjB,CAAC,CACF,CCLA,SAAS,EAAc,CAAE,YAAW,OAAO,SAAU,GAAG,GAAS,CAChE,OAAuB,EAAA,EAAA,IAAA,CAAI,SAAU,CACpC,GAAG,EACH,UAAW,EAAgB,wBAAyB,CAAS,EAC7D,MACD,CAAC,CACF,CCPA,SAAS,EAAiB,CAAE,YAAW,OAAO,QAAS,GAAG,GAAS,CAClE,OAAuB,EAAA,EAAA,IAAA,CAAI,OAAQ,CAClC,GAAG,EACH,UAAW,EAAgB,2BAA4B,CAAS,EAChE,YAAa,CACd,CAAC,CACF,CCHA,SAAS,EAAa,CAAE,WAAU,YAAW,SAAS,KAAM,GAAG,GAAS,CAEvE,OADA,EAAyB,GACF,EAAA,EAAA,IAAA,CAAI,MAAO,CACjC,GAAG,EACH,UAAW,EAAgB,iBAAkB,CAAS,EACtD,eAAgB,EAChB,UAA0B,EAAA,EAAA,IAAA,CAAI,EAAqB,CAClD,MAAO,GACP,UACD,CAAC,CACF,CAAC,CACF,CCdA,IAAM,EAAU,OAAO,OAAO,EAAW,CACxC,MAAO,EACP,UAAW,EACX,OAAQ,CACT,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-C-WdTj3F.js","names":[],"sources":["../../../../node_modules/.pnpm/@tinyrack+ui@0.
|
|
1
|
+
{"version":3,"file":"link-C-WdTj3F.js","names":[],"sources":["../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/link/link.js"],"sourcesContent":["\"use client\";\nimport { mergeClassNames } from \"../../internal/component-class-name.js\";\nimport { cloneElement, isValidElement } from \"react\";\nimport { useRender } from \"@base-ui/react/use-render\";\n//#region src/components/link/link.tsx\nfunction TRLink({ className, disabled = false, href, onClick, onKeyDown, ref, render, underline = \"hover\", variant = \"default\", ...props }) {\n\tconst resolvedRender = disabled && isValidElement(render) ? cloneElement(render, { href: void 0 }) : render;\n\treturn useRender({\n\t\tdefaultTagName: \"a\",\n\t\tprops: {\n\t\t\t...props,\n\t\t\t\"aria-disabled\": disabled || void 0,\n\t\t\tclassName: mergeClassNames(\"tr-link\", className),\n\t\t\t\"data-disabled\": disabled ? \"\" : void 0,\n\t\t\t\"data-underline\": underline,\n\t\t\t\"data-variant\": variant,\n\t\t\thref: disabled ? void 0 : href,\n\t\t\tonClick(event) {\n\t\t\t\tif (disabled) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tonClick?.(event);\n\t\t\t},\n\t\t\tonKeyDown(event) {\n\t\t\t\tif (disabled && (event.key === \"Enter\" || event.key === \" \")) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tonKeyDown?.(event);\n\t\t\t},\n\t\t\ttabIndex: disabled ? -1 : props.tabIndex\n\t\t},\n\t\tref,\n\t\trender: resolvedRender\n\t});\n}\n//#endregion\nexport { TRLink };\n\n//# sourceMappingURL=link.js.map"],"x_google_ignoreList":[0],"mappings":"uFAKA,SAAS,EAAO,CAAE,YAAW,WAAW,GAAO,OAAM,UAAS,YAAW,MAAK,SAAQ,YAAY,QAAS,UAAU,UAAW,GAAG,GAAS,CAC3I,IAAM,EAAiB,IAAA,EAAA,EAAA,eAAA,CAA2B,CAAM,GAAA,EAAA,EAAA,aAAA,CAAiB,EAAQ,CAAE,KAAM,IAAK,EAAE,CAAC,EAAI,EACrG,OAAO,EAAU,CAChB,eAAgB,IAChB,MAAO,CACN,GAAG,EACH,gBAAiB,GAAY,IAAK,GAClC,UAAW,EAAgB,UAAW,CAAS,EAC/C,gBAAiB,EAAW,GAAK,IAAK,GACtC,iBAAkB,EAClB,eAAgB,EAChB,KAAM,EAAW,IAAK,GAAI,EAC1B,QAAQ,EAAO,CACd,GAAI,EAAU,CACb,EAAM,eAAe,EACrB,EAAM,gBAAgB,EACtB,MACD,CACA,IAAU,CAAK,CAChB,EACA,UAAU,EAAO,CAChB,GAAI,IAAa,EAAM,MAAQ,SAAW,EAAM,MAAQ,KAAM,CAC7D,EAAM,eAAe,EACrB,EAAM,gBAAgB,EACtB,MACD,CACA,IAAY,CAAK,CAClB,EACA,SAAU,EAAW,GAAK,EAAM,QACjC,EACA,MACA,OAAQ,CACT,CAAC,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{J as e,Z as t,i as n,n as r,q as i,s as a}from"./createLucideIcon-BPpAIXR_.js";import{On as o,Sr as s,t as c}from"./config-DTQCUlfZ.js";import{t as l}from"./useMutation-BWZ0G4Td.js";import{
|
|
2
|
-
//# sourceMappingURL=login-
|
|
1
|
+
import{J as e,Z as t,i as n,n as r,q as i,s as a}from"./createLucideIcon-BPpAIXR_.js";import{On as o,Sr as s,t as c}from"./config-DTQCUlfZ.js";import{t as l}from"./useMutation-BWZ0G4Td.js";import{nt as u}from"./shell-header-bar-BPgCjFPv.js";import{t as d}from"./fingerprint-pattern-DP62Vk8x.js";import{t as f}from"./authorization-context-banner-DfkjwE93.js";import{a as p,n as m,r as h,t as g}from"./provider-logos-u1YURWQr.js";import{t as _}from"./mail-D0B_1Dzd.js";import{F as v,N as y,R as b,S as x,b as S,j as C,z as w}from"./index-H6CJKjiN.js";import{t as T}from"./auth-page-header-ZLhtQ7Et.js";import{t as E}from"./alert-leqyvHOp.js";import{t as D}from"./auth-layout-CcJy6dVY.js";import{t as O}from"./promise-OpBtq8tG.js";import{t as k}from"./passkey-error-ZqqPQUta.js";import{t as A}from"./passkey-J1qnYp9A.js";var j=t(e()),M=i(),N={google:(0,M.jsx)(h,{className:`size-tinyrack-xl`}),github:(0,M.jsx)(m,{className:`size-tinyrack-xl`}),apple:(0,M.jsx)(g,{className:`size-tinyrack-xl`})};function P({icon:e,label:t,providerType:n,render:i,onClick:o,isLoading:s,disabled:c}){let l;l=n&&n!==`generic_oauth`?N[n]:typeof e==`string`?(0,M.jsx)(`img`,{alt:``,className:`size-tinyrack-xl`,src:e}):e||(0,M.jsx)(p,{"aria-hidden":!0,className:`size-tinyrack-xl`});let u=(0,M.jsxs)(M.Fragment,{children:[(0,M.jsx)(`span`,{className:`flex size-tinyrack-xl shrink-0 items-center justify-center`,children:l}),(0,M.jsx)(r,{truncate:!0,variant:`body`,children:t})]}),d=`w-full justify-start gap-tinyrack-md transition-colors duration-tinyrack-fast ease-tinyrack-standard`;return i?(0,M.jsx)(w,{appearance:`outline`,className:`cursor-pointer ${d}`,intent:`neutral`,render:i,uiSize:`lg`,children:u}):(0,M.jsx)(a,{appearance:`outline`,className:d,disabled:c,intent:`neutral`,loading:s,onClick:o,type:`button`,uiSize:`lg`,children:u})}var F={access_denied:`oauth.error.accessDenied`,temporarily_unavailable:`oauth.error.temporarilyUnavailable`,server_error:`oauth.error.serverError`,registration_email_not_allowed:`oauth.error.registrationEmailNotAllowed`};function I(){let{t:e,i18n:t}=n(),r=o(),i=s(),a=S.useSearch(),p=a.lang??t.language,{data:m}=u(c),h=m.registration.signup_notice?.[p]??m.registration.signup_notice?.[m.i18n.fallback_language],g=m.identity_providers,w=a.oauth_error,N=w?e(F[w]??`oauth.error.failed`):void 0,I=m.auth.password.enabled,L=m.auth.passkey.enabled,[R,z]=(0,j.useState)(null),B=async e=>{e.user&&(i.setQueryData(x.queryKey,e),await O(),v(a)?window.location.href=C(a):r.navigate({to:`/profile`})),i.invalidateQueries({queryKey:x.queryKey})},V=l({...A,onSuccess:e=>{z(null),B(e)},onError:t=>{if(k(t)===`unsupported`){z(e(`login.passkey.error.unsupported`));return}z(e(`login.passkey.error.failed`))},onSettled:()=>{i.invalidateQueries({queryKey:x.queryKey})}}),H=e=>{let t=`/api/oauth/${e}/authorize?mode=login`;if(v(a)){let e=new URL(C(a)),n=`${e.pathname}${e.search}${e.hash}`;t+=`&return_url=${encodeURIComponent(n)}`}return t};return(0,M.jsxs)(D,{children:[(0,M.jsx)(T,{subtitle:e(`login.selectMethod.subtitle`)||void 0,title:e(`login.title`)}),(0,M.jsx)(f,{search:a}),N&&(0,M.jsx)(E,{icon:b,type:`error`,children:N}),R&&(0,M.jsx)(E,{icon:b,type:`error`,children:R}),(0,M.jsxs)(`div`,{className:`flex flex-col gap-tinyrack-sm`,children:[g.map(e=>(0,M.jsx)(P,{icon:e.icon_url,label:e.display_name,providerType:e.type,render:(0,M.jsx)(`a`,{href:H(e.id)})},e.id)),I&&(0,M.jsx)(P,{icon:(0,M.jsx)(_,{"aria-hidden":!0,className:`size-tinyrack-xl`}),label:e(`login.method.password`),render:(0,M.jsx)(`a`,{href:(()=>{let e=new URLSearchParams;for(let[t,n]of Object.entries(y(a)))if(n!==void 0){let r=String(n);e.set(t,t===`account_selected`?decodeURIComponent(r).replaceAll(`"`,``):r)}let t=e.toString();return t?`/login/password?${t}`:`/login/password`})()})}),L&&(0,M.jsx)(P,{disabled:V.isPending,icon:(0,M.jsx)(d,{"aria-hidden":!0,className:`size-tinyrack-xl`}),isLoading:V.isPending,label:e(`login.method.passkey`),onClick:()=>{z(null),V.mutate()}})]}),h&&(0,M.jsx)(`div`,{className:`prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!`,dangerouslySetInnerHTML:{__html:h}})]})}export{I as component};
|
|
2
|
+
//# sourceMappingURL=login-CBinfk4L.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-nukebli4.js","names":["useMutation","useQueryClient","useSuspenseQuery","useRouter","CircleAlertIcon","FingerprintIcon","MailIcon","useState","useTranslation","AuthMethodTile","AuthPageHeader","AuthorizationContextBanner","Alert","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","classifyPasskeyError","tick","appConfigQueryOptions","authenticateWithPasskeyMutationOptions","getSessionQueryOptions","OAUTH_ERROR_I18N_MAP","Record","access_denied","temporarily_unavailable","server_error","registration_email_not_allowed","Route","Login","t","i18n","router","queryClient","search","useSearch","lang","language","data","configData","implicitNotice","registration","signup_notice","fallback_language","oauthProviders","identity_providers","oauthError","oauth_error","oauthErrorMessage","undefined","isPasswordAuthEnabled","auth","password","enabled","isPasskeyEnabled","passkey","passkeyError","setPasskeyError","handlePasskeySuccess","AuthResponse","user","setQueryData","queryKey","window","location","href","navigate","to","invalidateQueries","passkeyLoginMutation","onSuccess","onError","error","reason","onSettled","buildOAuthUrl","providerId","oauthUrl","authUrl","URL","returnUrl","pathname","hash","encodeURIComponent","buildPasswordLoginHref","params","URLSearchParams","key","value","Object","entries","stringValue","String","set","decodeURIComponent","replaceAll","query","toString","map","provider","icon_url","id","display_name","type","isPending","mutate","__html","component"],"sources":["../../../frontend/src/components/auth/auth-method-tile.tsx","../../../frontend/src/routes/login/index.tsx?tsr-split=component"],"sourcesContent":["import { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLinkButton } from '@tinyrack/ui/components/link-button';\nimport { TRText } from '@tinyrack/ui/components/text';\nimport { LinkIcon } from 'lucide-react';\nimport type { ReactElement, ReactNode } from 'react';\nimport {\n AppleLogo,\n GithubLogo,\n GoogleLogo,\n} from '#frontend/components/auth/provider-logos.tsx';\nimport type { OAuthProviderType } from '#frontend/queries/config.ts';\n\ntype AuthMethodTileBaseProps = {\n /** A provider icon URL, or a ready-made element. */\n icon?: string | ReactNode;\n label: string;\n providerType?: OAuthProviderType;\n};\n\ntype AuthMethodTileProps = AuthMethodTileBaseProps &\n (\n | {\n /** Navigating method (OAuth, or the email form). Must be a real anchor. */\n render: ReactElement;\n onClick?: never;\n isLoading?: never;\n disabled?: never;\n }\n | {\n render?: never;\n /** In-page method (passkey), which runs a ceremony instead of navigating. */\n onClick: () => void;\n isLoading?: boolean;\n disabled?: boolean;\n }\n );\n\nconst PROVIDER_LOGOS: Record<\n Exclude<OAuthProviderType, 'generic_oauth'>,\n ReactNode\n> = {\n google: <GoogleLogo className=\"size-tinyrack-xl\" />,\n github: <GithubLogo className=\"size-tinyrack-xl\" />,\n apple: <AppleLogo className=\"size-tinyrack-xl\" />,\n};\n\n/**\n * One sign-in method on the method chooser.\n *\n * Full-width rows at every breakpoint. The previous three-across grid gave\n * each tile a third of the card, so provider names wrapped onto two lines and\n * the icons and labels never lined up between tiles.\n */\nexport function AuthMethodTile({\n icon,\n label,\n providerType,\n render,\n onClick,\n isLoading,\n disabled,\n}: AuthMethodTileProps) {\n let leading: ReactNode;\n if (providerType && providerType !== 'generic_oauth') {\n leading = PROVIDER_LOGOS[providerType];\n } else if (typeof icon === 'string') {\n leading = <img alt=\"\" className=\"size-tinyrack-xl\" src={icon} />;\n } else if (icon) {\n leading = icon;\n } else {\n leading = <LinkIcon aria-hidden className=\"size-tinyrack-xl\" />;\n }\n\n const content = (\n <>\n <span className=\"flex size-tinyrack-xl shrink-0 items-center justify-center\">\n {leading}\n </span>\n <TRText truncate variant=\"body\">\n {label}\n </TRText>\n </>\n );\n\n const className =\n 'w-full justify-start gap-tinyrack-md transition-colors duration-tinyrack-fast ease-tinyrack-standard';\n\n if (render) {\n return (\n <TRLinkButton\n appearance=\"outline\"\n className={`cursor-pointer ${className}`}\n intent=\"neutral\"\n render={render}\n uiSize=\"lg\"\n >\n {content}\n </TRLinkButton>\n );\n }\n\n return (\n <TRButton\n appearance=\"outline\"\n className={className}\n disabled={disabled}\n intent=\"neutral\"\n loading={isLoading}\n onClick={onClick}\n type=\"button\"\n uiSize=\"lg\"\n >\n {content}\n </TRButton>\n );\n}\n","import {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useRouter } from '@tanstack/react-router';\nimport { CircleAlertIcon, FingerprintIcon, MailIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthMethodTile } from '#frontend/components/auth/auth-method-tile.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n hasAuthorizationContext,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { classifyPasskeyError } from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport {\n type AppConfigs,\n appConfigQueryOptions,\n} from '#frontend/queries/config.ts';\nimport { authenticateWithPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema.extend({\n oauth_error: z.string().optional(),\n oauth_error_description: z.string().optional(),\n});\n\nconst OAUTH_ERROR_I18N_MAP: Record<string, string> = {\n access_denied: 'oauth.error.accessDenied',\n temporarily_unavailable: 'oauth.error.temporarilyUnavailable',\n server_error: 'oauth.error.serverError',\n registration_email_not_allowed: 'oauth.error.registrationEmailNotAllowed',\n};\n\nexport const Route = createFileRoute('/login/')({\n component: Login,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n\n if (shouldRedirectToPassword(config, deps.search)) {\n throw redirect({\n to: '/login/password',\n search: extractOAuthParams(deps.search),\n replace: true,\n });\n }\n\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction shouldRedirectToPassword(\n config: AppConfigs,\n search: z.infer<typeof SearchSchema>,\n): boolean {\n if (search.oauth_error) {\n return false;\n }\n\n return (\n config.auth.password.enabled &&\n !config.auth.passkey.enabled &&\n config.identity_providers.length === 0\n );\n}\n\nfunction Login() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const lang = search.lang ?? i18n.language;\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n // Get implicit notice from config for OAuth signup\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const oauthProviders = configData.identity_providers;\n\n const oauthError = search.oauth_error;\n const oauthErrorMessage = oauthError\n ? t(OAUTH_ERROR_I18N_MAP[oauthError] ?? 'oauth.error.failed')\n : undefined;\n\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const [passkeyError, setPasskeyError] = useState<string | null>(null);\n\n const handlePasskeySuccess = async (data: AuthResponse) => {\n if (data.user) {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, data);\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n };\n\n const passkeyLoginMutation = useMutation({\n ...authenticateWithPasskeyMutationOptions,\n onSuccess: (data) => {\n setPasskeyError(null);\n handlePasskeySuccess(data);\n },\n onError: (error) => {\n const reason = classifyPasskeyError(error);\n if (reason === 'unsupported') {\n setPasskeyError(t('login.passkey.error.unsupported'));\n return;\n }\n\n setPasskeyError(t('login.passkey.error.failed'));\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const buildOAuthUrl = (providerId: string) => {\n let oauthUrl = `/api/oauth/${providerId}/authorize?mode=login`;\n\n if (isOAuthFlow(search)) {\n const authUrl = new URL(buildAuthenticatedAuthorizeUrl(search));\n const returnUrl = `${authUrl.pathname}${authUrl.search}${authUrl.hash}`;\n oauthUrl += `&return_url=${encodeURIComponent(returnUrl)}`;\n }\n\n return oauthUrl;\n };\n\n const buildPasswordLoginHref = () => {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(extractOAuthParams(search))) {\n if (value !== undefined) {\n const stringValue = String(value);\n params.set(\n key,\n key === 'account_selected'\n ? decodeURIComponent(stringValue).replaceAll('\"', '')\n : stringValue,\n );\n }\n }\n const query = params.toString();\n return query ? `/login/password?${query}` : '/login/password';\n };\n\n return (\n <AuthLayout>\n {/*\n Branding title and subtitle are rendered once, by the brand panel in\n the layout. Repeating them here would give the page two copies of the\n product name and two heading nodes with the same text.\n */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {oauthErrorMessage && (\n <Alert icon={CircleAlertIcon} type=\"error\">\n {oauthErrorMessage}\n </Alert>\n )}\n\n {passkeyError && (\n <Alert icon={CircleAlertIcon} type=\"error\">\n {passkeyError}\n </Alert>\n )}\n\n <div className=\"flex flex-col gap-tinyrack-sm\">\n {oauthProviders.map((provider) => (\n <AuthMethodTile\n icon={provider.icon_url}\n key={provider.id}\n label={provider.display_name}\n providerType={provider.type}\n // Leaves the SPA for the provider, so a plain anchor rather than a\n // router link. No children here — the tile supplies them, and an\n // element passed to `render` would override them.\n render={<a href={buildOAuthUrl(provider.id)} />}\n />\n ))}\n\n {isPasswordAuthEnabled && (\n <AuthMethodTile\n icon={<MailIcon aria-hidden className=\"size-tinyrack-xl\" />}\n label={t('login.method.password')}\n render={<a href={buildPasswordLoginHref()} />}\n />\n )}\n\n {isPasskeyEnabled && (\n <AuthMethodTile\n disabled={passkeyLoginMutation.isPending}\n icon={<FingerprintIcon aria-hidden className=\"size-tinyrack-xl\" />}\n isLoading={passkeyLoginMutation.isPending}\n label={t('login.method.passkey')}\n onClick={() => {\n setPasskeyError(null);\n passkeyLoginMutation.mutate();\n }}\n />\n )}\n </div>\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login-CBinfk4L.js","names":["useMutation","useQueryClient","useSuspenseQuery","useRouter","CircleAlertIcon","FingerprintIcon","MailIcon","useState","useTranslation","AuthMethodTile","AuthPageHeader","AuthorizationContextBanner","Alert","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","classifyPasskeyError","tick","appConfigQueryOptions","authenticateWithPasskeyMutationOptions","getSessionQueryOptions","OAUTH_ERROR_I18N_MAP","Record","access_denied","temporarily_unavailable","server_error","registration_email_not_allowed","Route","Login","t","i18n","router","queryClient","search","useSearch","lang","language","data","configData","implicitNotice","registration","signup_notice","fallback_language","oauthProviders","identity_providers","oauthError","oauth_error","oauthErrorMessage","undefined","isPasswordAuthEnabled","auth","password","enabled","isPasskeyEnabled","passkey","passkeyError","setPasskeyError","handlePasskeySuccess","AuthResponse","user","setQueryData","queryKey","window","location","href","navigate","to","invalidateQueries","passkeyLoginMutation","onSuccess","onError","error","reason","onSettled","buildOAuthUrl","providerId","oauthUrl","authUrl","URL","returnUrl","pathname","hash","encodeURIComponent","buildPasswordLoginHref","params","URLSearchParams","key","value","Object","entries","stringValue","String","set","decodeURIComponent","replaceAll","query","toString","map","provider","icon_url","id","display_name","type","isPending","mutate","__html","component"],"sources":["../../../frontend/src/components/auth/auth-method-tile.tsx","../../../frontend/src/routes/login/index.tsx?tsr-split=component"],"sourcesContent":["import { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLinkButton } from '@tinyrack/ui/components/link-button';\nimport { TRText } from '@tinyrack/ui/components/text';\nimport { LinkIcon } from 'lucide-react';\nimport type { ReactElement, ReactNode } from 'react';\nimport {\n AppleLogo,\n GithubLogo,\n GoogleLogo,\n} from '#frontend/components/auth/provider-logos.tsx';\nimport type { OAuthProviderType } from '#frontend/queries/config.ts';\n\ntype AuthMethodTileBaseProps = {\n /** A provider icon URL, or a ready-made element. */\n icon?: string | ReactNode;\n label: string;\n providerType?: OAuthProviderType;\n};\n\ntype AuthMethodTileProps = AuthMethodTileBaseProps &\n (\n | {\n /** Navigating method (OAuth, or the email form). Must be a real anchor. */\n render: ReactElement;\n onClick?: never;\n isLoading?: never;\n disabled?: never;\n }\n | {\n render?: never;\n /** In-page method (passkey), which runs a ceremony instead of navigating. */\n onClick: () => void;\n isLoading?: boolean;\n disabled?: boolean;\n }\n );\n\nconst PROVIDER_LOGOS: Record<\n Exclude<OAuthProviderType, 'generic_oauth'>,\n ReactNode\n> = {\n google: <GoogleLogo className=\"size-tinyrack-xl\" />,\n github: <GithubLogo className=\"size-tinyrack-xl\" />,\n apple: <AppleLogo className=\"size-tinyrack-xl\" />,\n};\n\n/**\n * One sign-in method on the method chooser.\n *\n * Full-width rows at every breakpoint. The previous three-across grid gave\n * each tile a third of the card, so provider names wrapped onto two lines and\n * the icons and labels never lined up between tiles.\n */\nexport function AuthMethodTile({\n icon,\n label,\n providerType,\n render,\n onClick,\n isLoading,\n disabled,\n}: AuthMethodTileProps) {\n let leading: ReactNode;\n if (providerType && providerType !== 'generic_oauth') {\n leading = PROVIDER_LOGOS[providerType];\n } else if (typeof icon === 'string') {\n leading = <img alt=\"\" className=\"size-tinyrack-xl\" src={icon} />;\n } else if (icon) {\n leading = icon;\n } else {\n leading = <LinkIcon aria-hidden className=\"size-tinyrack-xl\" />;\n }\n\n const content = (\n <>\n <span className=\"flex size-tinyrack-xl shrink-0 items-center justify-center\">\n {leading}\n </span>\n <TRText truncate variant=\"body\">\n {label}\n </TRText>\n </>\n );\n\n const className =\n 'w-full justify-start gap-tinyrack-md transition-colors duration-tinyrack-fast ease-tinyrack-standard';\n\n if (render) {\n return (\n <TRLinkButton\n appearance=\"outline\"\n className={`cursor-pointer ${className}`}\n intent=\"neutral\"\n render={render}\n uiSize=\"lg\"\n >\n {content}\n </TRLinkButton>\n );\n }\n\n return (\n <TRButton\n appearance=\"outline\"\n className={className}\n disabled={disabled}\n intent=\"neutral\"\n loading={isLoading}\n onClick={onClick}\n type=\"button\"\n uiSize=\"lg\"\n >\n {content}\n </TRButton>\n );\n}\n","import {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useRouter } from '@tanstack/react-router';\nimport { CircleAlertIcon, FingerprintIcon, MailIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthMethodTile } from '#frontend/components/auth/auth-method-tile.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n hasAuthorizationContext,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { classifyPasskeyError } from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport {\n type AppConfigs,\n appConfigQueryOptions,\n} from '#frontend/queries/config.ts';\nimport { authenticateWithPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema.extend({\n oauth_error: z.string().optional(),\n oauth_error_description: z.string().optional(),\n});\n\nconst OAUTH_ERROR_I18N_MAP: Record<string, string> = {\n access_denied: 'oauth.error.accessDenied',\n temporarily_unavailable: 'oauth.error.temporarilyUnavailable',\n server_error: 'oauth.error.serverError',\n registration_email_not_allowed: 'oauth.error.registrationEmailNotAllowed',\n};\n\nexport const Route = createFileRoute('/login/')({\n component: Login,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n\n if (shouldRedirectToPassword(config, deps.search)) {\n throw redirect({\n to: '/login/password',\n search: extractOAuthParams(deps.search),\n replace: true,\n });\n }\n\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction shouldRedirectToPassword(\n config: AppConfigs,\n search: z.infer<typeof SearchSchema>,\n): boolean {\n if (search.oauth_error) {\n return false;\n }\n\n return (\n config.auth.password.enabled &&\n !config.auth.passkey.enabled &&\n config.identity_providers.length === 0\n );\n}\n\nfunction Login() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const lang = search.lang ?? i18n.language;\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n // Get implicit notice from config for OAuth signup\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const oauthProviders = configData.identity_providers;\n\n const oauthError = search.oauth_error;\n const oauthErrorMessage = oauthError\n ? t(OAUTH_ERROR_I18N_MAP[oauthError] ?? 'oauth.error.failed')\n : undefined;\n\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const [passkeyError, setPasskeyError] = useState<string | null>(null);\n\n const handlePasskeySuccess = async (data: AuthResponse) => {\n if (data.user) {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, data);\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n };\n\n const passkeyLoginMutation = useMutation({\n ...authenticateWithPasskeyMutationOptions,\n onSuccess: (data) => {\n setPasskeyError(null);\n handlePasskeySuccess(data);\n },\n onError: (error) => {\n const reason = classifyPasskeyError(error);\n if (reason === 'unsupported') {\n setPasskeyError(t('login.passkey.error.unsupported'));\n return;\n }\n\n setPasskeyError(t('login.passkey.error.failed'));\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const buildOAuthUrl = (providerId: string) => {\n let oauthUrl = `/api/oauth/${providerId}/authorize?mode=login`;\n\n if (isOAuthFlow(search)) {\n const authUrl = new URL(buildAuthenticatedAuthorizeUrl(search));\n const returnUrl = `${authUrl.pathname}${authUrl.search}${authUrl.hash}`;\n oauthUrl += `&return_url=${encodeURIComponent(returnUrl)}`;\n }\n\n return oauthUrl;\n };\n\n const buildPasswordLoginHref = () => {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(extractOAuthParams(search))) {\n if (value !== undefined) {\n const stringValue = String(value);\n params.set(\n key,\n key === 'account_selected'\n ? decodeURIComponent(stringValue).replaceAll('\"', '')\n : stringValue,\n );\n }\n }\n const query = params.toString();\n return query ? `/login/password?${query}` : '/login/password';\n };\n\n return (\n <AuthLayout>\n {/*\n Branding title and subtitle are rendered once, by the brand panel in\n the layout. Repeating them here would give the page two copies of the\n product name and two heading nodes with the same text.\n */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {oauthErrorMessage && (\n <Alert icon={CircleAlertIcon} type=\"error\">\n {oauthErrorMessage}\n </Alert>\n )}\n\n {passkeyError && (\n <Alert icon={CircleAlertIcon} type=\"error\">\n {passkeyError}\n </Alert>\n )}\n\n <div className=\"flex flex-col gap-tinyrack-sm\">\n {oauthProviders.map((provider) => (\n <AuthMethodTile\n icon={provider.icon_url}\n key={provider.id}\n label={provider.display_name}\n providerType={provider.type}\n // Leaves the SPA for the provider, so a plain anchor rather than a\n // router link. No children here — the tile supplies them, and an\n // element passed to `render` would override them.\n render={<a href={buildOAuthUrl(provider.id)} />}\n />\n ))}\n\n {isPasswordAuthEnabled && (\n <AuthMethodTile\n icon={<MailIcon aria-hidden className=\"size-tinyrack-xl\" />}\n label={t('login.method.password')}\n render={<a href={buildPasswordLoginHref()} />}\n />\n )}\n\n {isPasskeyEnabled && (\n <AuthMethodTile\n disabled={passkeyLoginMutation.isPending}\n icon={<FingerprintIcon aria-hidden className=\"size-tinyrack-xl\" />}\n isLoading={passkeyLoginMutation.isPending}\n label={t('login.method.passkey')}\n onClick={() => {\n setPasskeyError(null);\n passkeyLoginMutation.mutate();\n }}\n />\n )}\n </div>\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-
|
|
1
|
+
{"version":3,"file":"login-tzTSqiOS.js","names":["RouteErrorFallback","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/login/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, redirect, useRouter } from '@tanstack/react-router';\nimport { CircleAlertIcon, FingerprintIcon, MailIcon } from 'lucide-react';\nimport { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthMethodTile } from '#frontend/components/auth/auth-method-tile.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n hasAuthorizationContext,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport { classifyPasskeyError } from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport {\n type AppConfigs,\n appConfigQueryOptions,\n} from '#frontend/queries/config.ts';\nimport { authenticateWithPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema.extend({\n oauth_error: z.string().optional(),\n oauth_error_description: z.string().optional(),\n});\n\nconst OAUTH_ERROR_I18N_MAP: Record<string, string> = {\n access_denied: 'oauth.error.accessDenied',\n temporarily_unavailable: 'oauth.error.temporarilyUnavailable',\n server_error: 'oauth.error.serverError',\n registration_email_not_allowed: 'oauth.error.registrationEmailNotAllowed',\n};\n\nexport const Route = createFileRoute('/login/')({\n component: Login,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n const config = await context.queryClient.ensureQueryData(\n appConfigQueryOptions,\n );\n\n if (shouldRedirectToPassword(config, deps.search)) {\n throw redirect({\n to: '/login/password',\n search: extractOAuthParams(deps.search),\n replace: true,\n });\n }\n\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction shouldRedirectToPassword(\n config: AppConfigs,\n search: z.infer<typeof SearchSchema>,\n): boolean {\n if (search.oauth_error) {\n return false;\n }\n\n return (\n config.auth.password.enabled &&\n !config.auth.passkey.enabled &&\n config.identity_providers.length === 0\n );\n}\n\nfunction Login() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n\n const lang = search.lang ?? i18n.language;\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n // Get implicit notice from config for OAuth signup\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const oauthProviders = configData.identity_providers;\n\n const oauthError = search.oauth_error;\n const oauthErrorMessage = oauthError\n ? t(OAUTH_ERROR_I18N_MAP[oauthError] ?? 'oauth.error.failed')\n : undefined;\n\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const [passkeyError, setPasskeyError] = useState<string | null>(null);\n\n const handlePasskeySuccess = async (data: AuthResponse) => {\n if (data.user) {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, data);\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n };\n\n const passkeyLoginMutation = useMutation({\n ...authenticateWithPasskeyMutationOptions,\n onSuccess: (data) => {\n setPasskeyError(null);\n handlePasskeySuccess(data);\n },\n onError: (error) => {\n const reason = classifyPasskeyError(error);\n if (reason === 'unsupported') {\n setPasskeyError(t('login.passkey.error.unsupported'));\n return;\n }\n\n setPasskeyError(t('login.passkey.error.failed'));\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const buildOAuthUrl = (providerId: string) => {\n let oauthUrl = `/api/oauth/${providerId}/authorize?mode=login`;\n\n if (isOAuthFlow(search)) {\n const authUrl = new URL(buildAuthenticatedAuthorizeUrl(search));\n const returnUrl = `${authUrl.pathname}${authUrl.search}${authUrl.hash}`;\n oauthUrl += `&return_url=${encodeURIComponent(returnUrl)}`;\n }\n\n return oauthUrl;\n };\n\n const buildPasswordLoginHref = () => {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(extractOAuthParams(search))) {\n if (value !== undefined) {\n const stringValue = String(value);\n params.set(\n key,\n key === 'account_selected'\n ? decodeURIComponent(stringValue).replaceAll('\"', '')\n : stringValue,\n );\n }\n }\n const query = params.toString();\n return query ? `/login/password?${query}` : '/login/password';\n };\n\n return (\n <AuthLayout>\n {/*\n Branding title and subtitle are rendered once, by the brand panel in\n the layout. Repeating them here would give the page two copies of the\n product name and two heading nodes with the same text.\n */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {oauthErrorMessage && (\n <Alert icon={CircleAlertIcon} type=\"error\">\n {oauthErrorMessage}\n </Alert>\n )}\n\n {passkeyError && (\n <Alert icon={CircleAlertIcon} type=\"error\">\n {passkeyError}\n </Alert>\n )}\n\n <div className=\"flex flex-col gap-tinyrack-sm\">\n {oauthProviders.map((provider) => (\n <AuthMethodTile\n icon={provider.icon_url}\n key={provider.id}\n label={provider.display_name}\n providerType={provider.type}\n // Leaves the SPA for the provider, so a plain anchor rather than a\n // router link. No children here — the tile supplies them, and an\n // element passed to `render` would override them.\n render={<a href={buildOAuthUrl(provider.id)} />}\n />\n ))}\n\n {isPasswordAuthEnabled && (\n <AuthMethodTile\n icon={<MailIcon aria-hidden className=\"size-tinyrack-xl\" />}\n label={t('login.method.password')}\n render={<a href={buildPasswordLoginHref()} />}\n />\n )}\n\n {isPasskeyEnabled && (\n <AuthMethodTile\n disabled={passkeyLoginMutation.isPending}\n icon={<FingerprintIcon aria-hidden className=\"size-tinyrack-xl\" />}\n isLoading={passkeyLoginMutation.isPending}\n label={t('login.method.passkey')}\n onClick={() => {\n setPasskeyError(null);\n passkeyLoginMutation.mutate();\n }}\n />\n )}\n </div>\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"wCAcsF,IAAAC,EAA7ED"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{G as e,J as t,Z as n,c as r,q as i}from"./createLucideIcon-BPpAIXR_.js";import{_ as a,bn as o,pt as s}from"./config-DTQCUlfZ.js";import{
|
|
2
|
-
//# sourceMappingURL=modal-
|
|
1
|
+
import{G as e,J as t,Z as n,c as r,q as i}from"./createLucideIcon-BPpAIXR_.js";import{_ as a,bn as o,pt as s}from"./config-DTQCUlfZ.js";import{B as c,H as l,P as u,tt as d,u as f}from"./shell-header-bar-BPgCjFPv.js";import{a as p,c as m,d as h,f as g,i as _,l as v,n as y,o as b,r as x,s as S,u as C}from"./DialogHandle-ChG5Sj7T.js";var w=n(t(),1),T=w.forwardRef(function(e,t){let{render:n,className:i,style:o,forceRender:s=!1,...c}=e,l=g(),u=l.useState(`open`),d=l.useState(`nested`),f=l.useState(`mounted`);return r(`div`,e,{state:{open:u,transitionStatus:l.useState(`transitionStatus`)},ref:[l.context.backdropRef,t],stateAttributesMapping:a,props:[{role:`presentation`,hidden:!f,style:{userSelect:`none`,WebkitUserSelect:`none`}},c],enabled:s||!d})}),E=i(),D=w.forwardRef(function(e,t){let{render:n,className:i,style:a,finalFocus:o,initialFocus:u,...p}=e,m=g(),h=m.useState(`descriptionElementId`),y=m.useState(`disablePointerDismissal`),b=m.useState(`floatingRootContext`),x=m.useState(`popupProps`),S=m.useState(`modal`),C=m.useState(`mounted`),w=m.useState(`nested`),T=m.useState(`nestedOpenDialogCount`),D=m.useState(`open`),O=m.useState(`openMethod`),k=m.useState(`titleElementId`),A=m.useState(`transitionStatus`),j=m.useState(`role`),M=b.useState(`floatingId`);v(),s({open:D,ref:m.context.popupRef,onComplete(){D&&m.context.onOpenChangeComplete?.(!0)}});let N=u===void 0?l(m.context.popupRef):u,P=T>0,F=m.useStateSetter(`popupElement`),I=r(`div`,e,{state:{open:D,nested:w,transitionStatus:A,nestedDialogOpen:P},props:[x,{id:M,"aria-labelledby":k,"aria-describedby":h,role:j,...c,hidden:!C,onKeyDown(e){f.has(e.key)&&e.stopPropagation()},style:{"--nested-dialogs":T}},p],ref:[t,m.context.popupRef,F],stateAttributesMapping:_});return(0,E.jsx)(d,{context:b,openInteractionType:O,disabled:!C,closeOnFocusOut:!y,initialFocus:N,returnFocus:o,modal:S!==!1,restoreFocus:`popup`,children:I})});function O(e){return S(`dialog`,e)}var k=o(T,`tr-layer-backdrop tr-dialog-backdrop`),A=o(h,`tr-dialog-close`),j=o(C,`tr-dialog-description`);function M(){return y()}function N({className:t,placement:n=`middle`,...r}){return(0,E.jsx)(D,{...r,className:e(`tr-dialog tr-dialog-box`,t),"data-placement":n})}var P=o(m);function F(e){return(0,E.jsx)(O,{...e})}var I=o(b,`tr-dialog-title`),L=o(p,`tr-dialog-trigger`),R={Backdrop:k,Close:A,Description:j,Popup:N,Portal:P,Root:F,Viewport:o(x,`tr-layer-viewport tr-dialog-viewport`),Title:I,Trigger:L,createHandle:M},z={sm:`[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-sm)]`,md:`[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-md)]`,lg:`[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-md)]`},B={default:`bg-tinyrack-surface-muted text-tinyrack-text`,destructive:`bg-tinyrack-danger-surface text-tinyrack-danger-foreground`};function V({isOpen:e,onClose:t,title:n,description:r,children:i,size:a=`md`,preventClose:o=!1,icon:s,variant:c=`default`}){return(0,w.useEffect)(()=>{if(!e||o)return;let n=e=>{e.key===`Escape`&&t()};return document.addEventListener(`keydown`,n),()=>document.removeEventListener(`keydown`,n)},[e,o,t]),e?(0,E.jsx)(R.Root,{defaultOpen:!0,disablePointerDismissal:o,onOpenChange:e=>{!e&&!o&&t()},children:(0,E.jsx)(R.Portal,{children:(0,E.jsxs)(R.Viewport,{children:[(0,E.jsx)(R.Backdrop,{}),(0,E.jsxs)(R.Popup,{className:z[a],children:[(0,E.jsxs)(`div`,{className:`flex items-start gap-tinyrack-md`,children:[s&&(0,E.jsx)(`div`,{className:`flex size-tinyrack-2xl shrink-0 items-center justify-center rounded-tinyrack-full ${B[c]}`,children:(0,E.jsx)(s,{"aria-hidden":!0,className:`size-tinyrack-lg`})}),(0,E.jsxs)(`div`,{className:`flex min-w-0 flex-1 flex-col gap-tinyrack-3xs`,children:[(0,E.jsx)(R.Title,{children:n}),r&&(0,E.jsx)(R.Description,{className:`text-tinyrack-text-muted text-tinyrack-xs`,children:r})]}),!o&&(0,E.jsx)(R.Close,{"aria-label":`Close`,className:`shrink-0`,"data-testid":`modal-close`,children:(0,E.jsx)(u,{"aria-hidden":!0,className:`size-tinyrack-lg`})})]}),i]})]})})}):null}function H({children:e}){return(0,E.jsx)(`div`,{className:`flex justify-end gap-tinyrack-sm pt-tinyrack-lg`,children:e})}export{H as n,V as t};
|
|
2
|
+
//# sourceMappingURL=modal-CwoBUXp3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal-BDQFucjz.js","names":["Dialog.Backdrop","Dialog.Close","Dialog.Description","Dialog.createHandle","Dialog.Popup","Dialog.Portal","Dialog.Root","Dialog.Title","Dialog.Trigger","Dialog.Viewport"],"sources":["../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/dialog/backdrop/DialogBackdrop.mjs","../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/dialog/popup/DialogPopup.mjs","../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/dialog/root/DialogRoot.mjs","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-backdrop.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-close.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-description.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-handle.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-popup.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-portal.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-root.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-title.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-trigger.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-viewport.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.25.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.3.1_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/index.js","../../../frontend/src/components/ui/modal.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useDialogRootContext } from \"../root/DialogRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\nimport { popupTransitionStateMapping } from \"../../utils/popupStateMapping.mjs\";\n\n/**\n * An overlay displayed beneath the popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogBackdrop = /*#__PURE__*/React.forwardRef(function DialogBackdrop(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n forceRender = false,\n ...elementProps\n } = componentProps;\n const store = useDialogRootContext();\n const open = store.useState('open');\n const nested = store.useState('nested');\n const mounted = store.useState('mounted');\n const transitionStatus = store.useState('transitionStatus');\n const state = {\n open,\n transitionStatus\n };\n return useRenderElement('div', componentProps, {\n state,\n ref: [store.context.backdropRef, forwardedRef],\n stateAttributesMapping: popupTransitionStateMapping,\n props: [{\n role: 'presentation',\n hidden: !mounted,\n style: {\n userSelect: 'none',\n WebkitUserSelect: 'none'\n }\n }, elementProps],\n enabled: forceRender || !nested\n });\n});\nif (process.env.NODE_ENV !== \"production\") DialogBackdrop.displayName = \"DialogBackdrop\";","'use client';\n\nimport * as React from 'react';\nimport { FloatingFocusManager } from \"../../floating-ui-react/index.mjs\";\nimport { useDialogRootContext } from \"../root/DialogRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\nimport { useDialogPortalContext } from \"../portal/DialogPortalContext.mjs\";\nimport { useOpenChangeComplete } from \"../../internals/useOpenChangeComplete.mjs\";\nimport { COMPOSITE_KEYS } from \"../../internals/composite/composite.mjs\";\nimport { FOCUSABLE_POPUP_PROPS, createDefaultInitialFocus } from \"../../utils/popups/index.mjs\";\nimport { dialogStateAttributesMapping } from \"../utils/stateAttributesMapping.mjs\";\n\n/**\n * A container for the dialog contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport const DialogPopup = /*#__PURE__*/React.forwardRef(function DialogPopup(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n finalFocus,\n initialFocus,\n ...elementProps\n } = componentProps;\n const store = useDialogRootContext();\n const descriptionElementId = store.useState('descriptionElementId');\n const disablePointerDismissal = store.useState('disablePointerDismissal');\n const floatingRootContext = store.useState('floatingRootContext');\n const rootPopupProps = store.useState('popupProps');\n const modal = store.useState('modal');\n const mounted = store.useState('mounted');\n const nested = store.useState('nested');\n const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');\n const open = store.useState('open');\n const openMethod = store.useState('openMethod');\n const titleElementId = store.useState('titleElementId');\n const transitionStatus = store.useState('transitionStatus');\n const role = store.useState('role');\n const floatingId = floatingRootContext.useState('floatingId');\n useDialogPortalContext();\n useOpenChangeComplete({\n open,\n ref: store.context.popupRef,\n onComplete() {\n if (open) {\n store.context.onOpenChangeComplete?.(true);\n }\n }\n });\n const resolvedInitialFocus = initialFocus === undefined ? createDefaultInitialFocus(store.context.popupRef) : initialFocus;\n const nestedDialogOpen = nestedOpenDialogCount > 0;\n const setPopupElement = store.useStateSetter('popupElement');\n const state = {\n open,\n nested,\n transitionStatus,\n nestedDialogOpen\n };\n const element = useRenderElement('div', componentProps, {\n state,\n props: [rootPopupProps, {\n id: floatingId,\n 'aria-labelledby': titleElementId,\n 'aria-describedby': descriptionElementId,\n role,\n ...FOCUSABLE_POPUP_PROPS,\n hidden: !mounted,\n onKeyDown(event) {\n if (COMPOSITE_KEYS.has(event.key)) {\n event.stopPropagation();\n }\n },\n style: {\n '--nested-dialogs': nestedOpenDialogCount\n }\n }, elementProps],\n ref: [forwardedRef, store.context.popupRef, setPopupElement],\n stateAttributesMapping: dialogStateAttributesMapping\n });\n return /*#__PURE__*/_jsx(FloatingFocusManager, {\n context: floatingRootContext,\n openInteractionType: openMethod,\n disabled: !mounted,\n closeOnFocusOut: !disablePointerDismissal,\n initialFocus: resolvedInitialFocus,\n returnFocus: finalFocus,\n modal: modal !== false,\n restoreFocus: \"popup\",\n children: element\n });\n});\nif (process.env.NODE_ENV !== \"production\") DialogPopup.displayName = \"DialogPopup\";","'use client';\n\nimport { useRenderDialogRoot } from \"./useRenderDialogRoot.mjs\";\n\n/**\n * Groups all parts of the dialog.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport function DialogRoot(props) {\n return useRenderDialogRoot('dialog', props);\n}","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-backdrop.tsx\nconst TRDialogBackdrop = createComponentPart(Dialog.Backdrop, \"tr-layer-backdrop tr-dialog-backdrop\");\n//#endregion\nexport { TRDialogBackdrop };\n\n//# sourceMappingURL=dialog-backdrop.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-close.tsx\nconst TRDialogClose = createComponentPart(Dialog.Close, \"tr-dialog-close\");\n//#endregion\nexport { TRDialogClose };\n\n//# sourceMappingURL=dialog-close.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-description.tsx\nconst TRDialogDescription = createComponentPart(Dialog.Description, \"tr-dialog-description\");\n//#endregion\nexport { TRDialogDescription };\n\n//# sourceMappingURL=dialog-description.js.map","\"use client\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-handle.ts\nfunction createDialogHandle() {\n\treturn Dialog.createHandle();\n}\n//#endregion\nexport { createDialogHandle };\n\n//# sourceMappingURL=dialog-handle.js.map","\"use client\";\nimport { mergeComponentClassName } from \"../../internal/component-class-name.js\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-popup.tsx\nfunction TRDialogPopup({ className, placement = \"middle\", ...props }) {\n\treturn /* @__PURE__ */ jsx(Dialog.Popup, {\n\t\t...props,\n\t\tclassName: mergeComponentClassName(\"tr-dialog tr-dialog-box\", className),\n\t\t\"data-placement\": placement\n\t});\n}\n//#endregion\nexport { TRDialogPopup };\n\n//# sourceMappingURL=dialog-popup.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-portal.tsx\nconst TRDialogPortal = createComponentPart(Dialog.Portal);\n//#endregion\nexport { TRDialogPortal };\n\n//# sourceMappingURL=dialog-portal.js.map","\"use client\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-root.tsx\nfunction TRDialogRoot(props) {\n\treturn /* @__PURE__ */ jsx(Dialog.Root, { ...props });\n}\n//#endregion\nexport { TRDialogRoot };\n\n//# sourceMappingURL=dialog-root.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-title.tsx\nconst TRDialogTitle = createComponentPart(Dialog.Title, \"tr-dialog-title\");\n//#endregion\nexport { TRDialogTitle };\n\n//# sourceMappingURL=dialog-title.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-trigger.tsx\nconst TRDialogTrigger = createComponentPart(Dialog.Trigger, \"tr-dialog-trigger\");\n//#endregion\nexport { TRDialogTrigger };\n\n//# sourceMappingURL=dialog-trigger.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-viewport.tsx\nconst TRDialogViewport = createComponentPart(Dialog.Viewport, \"tr-layer-viewport tr-dialog-viewport\");\n//#endregion\nexport { TRDialogViewport };\n\n//# sourceMappingURL=dialog-viewport.js.map","import { TRDialogBackdrop } from \"./dialog-backdrop.js\";\nimport { TRDialogClose } from \"./dialog-close.js\";\nimport { TRDialogDescription } from \"./dialog-description.js\";\nimport { createDialogHandle } from \"./dialog-handle.js\";\nimport { TRDialogPopup } from \"./dialog-popup.js\";\nimport { TRDialogPortal } from \"./dialog-portal.js\";\nimport { TRDialogRoot } from \"./dialog-root.js\";\nimport { TRDialogTitle } from \"./dialog-title.js\";\nimport { TRDialogTrigger } from \"./dialog-trigger.js\";\nimport { TRDialogViewport } from \"./dialog-viewport.js\";\n//#region src/components/dialog/index.tsx\nconst TRDialog = {\n\tBackdrop: TRDialogBackdrop,\n\tClose: TRDialogClose,\n\tDescription: TRDialogDescription,\n\tPopup: TRDialogPopup,\n\tPortal: TRDialogPortal,\n\tRoot: TRDialogRoot,\n\tViewport: TRDialogViewport,\n\tTitle: TRDialogTitle,\n\tTrigger: TRDialogTrigger,\n\tcreateHandle: createDialogHandle\n};\n//#endregion\nexport { TRDialog, TRDialogBackdrop, TRDialogClose, TRDialogDescription, TRDialogPopup, TRDialogPortal, TRDialogRoot, TRDialogTitle, TRDialogTrigger, TRDialogViewport, createDialogHandle };\n\n//# sourceMappingURL=index.js.map","import { TRDialog } from '@tinyrack/ui/components/dialog';\nimport type { LucideIcon } from 'lucide-react';\nimport { XIcon } from 'lucide-react';\nimport { type ReactNode, useEffect } from 'react';\n\ntype ModalVariant = 'default' | 'destructive';\n\ninterface ModalProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description?: string;\n children: ReactNode;\n size?: 'sm' | 'md' | 'lg';\n preventClose?: boolean;\n icon?: LucideIcon;\n variant?: ModalVariant;\n}\n\n/*\n Set through `--tr-dialog-box-max-width`, the override hook `.tr-dialog-box`\n exposes, rather than a `max-w-*` utility. The box feeds that same custom\n property into its own `width: min(…)` calculation, so a utility would move\n the max-width while leaving the width computation on the old value.\n\n This is the current design system overlay scale (20/32rem), not Tailwind's\n width scale. The local `lg` compatibility option now resolves to the largest\n supported overlay token, and `md` remains the unstyled `TRDialog` default.\n*/\nconst sizeClasses = {\n sm: '[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-sm)]',\n md: '[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-md)]',\n lg: '[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-md)]',\n} as const;\n\nconst iconVariantClasses: Record<ModalVariant, string> = {\n default: 'bg-tinyrack-surface-muted text-tinyrack-text',\n destructive: 'bg-tinyrack-danger-surface text-tinyrack-danger',\n};\n\nexport function Modal({\n isOpen,\n onClose,\n title,\n description,\n children,\n size = 'md',\n preventClose = false,\n icon: IconComponent,\n variant = 'default',\n}: ModalProps) {\n useEffect(() => {\n if (!isOpen) return;\n if (preventClose) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, preventClose, onClose]);\n\n if (!isOpen) {\n return null;\n }\n\n return (\n <TRDialog.Root\n defaultOpen\n disablePointerDismissal={preventClose}\n onOpenChange={(open) => {\n if (!open && !preventClose) {\n onClose();\n }\n }}\n >\n <TRDialog.Portal>\n <TRDialog.Viewport>\n <TRDialog.Backdrop />\n {/*\n No max-height here: `.tr-dialog-box` already clamps itself against\n `100dvh` minus the viewport gap, which is more correct than the\n `90vh`/`85vh` this used to override it with.\n */}\n <TRDialog.Popup className={sizeClasses[size]}>\n <div className=\"flex items-start gap-tinyrack-md\">\n {IconComponent && (\n <div\n className={`flex size-tinyrack-2xl shrink-0 items-center justify-center rounded-tinyrack-full ${iconVariantClasses[variant]}`}\n >\n <IconComponent aria-hidden className=\"size-tinyrack-lg\" />\n </div>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-tinyrack-3xs\">\n <TRDialog.Title>{title}</TRDialog.Title>\n {description && (\n <TRDialog.Description className=\"text-tinyrack-text-muted text-tinyrack-xs\">\n {description}\n </TRDialog.Description>\n )}\n </div>\n {!preventClose && (\n <TRDialog.Close\n aria-label=\"Close\"\n className=\"shrink-0\"\n data-testid=\"modal-close\"\n >\n <XIcon aria-hidden className=\"size-tinyrack-lg\" />\n </TRDialog.Close>\n )}\n </div>\n {children}\n </TRDialog.Popup>\n </TRDialog.Viewport>\n </TRDialog.Portal>\n </TRDialog.Root>\n );\n}\n\ninterface ModalActionsProps {\n children: ReactNode;\n}\n\nexport function ModalActions({ children }: ModalActionsProps) {\n return (\n <div className=\"flex justify-end gap-tinyrack-sm pt-tinyrack-lg\">\n {children}\n </div>\n );\n}\n"],"x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"mappings":"4VAaa,EAA8B,EAAM,WAAW,SAAwB,EAAgB,EAAc,CAChH,GAAM,CACJ,SACA,YACA,QACA,cAAc,GACd,GAAG,GACD,EACE,EAAQ,EAAqB,EAC7B,EAAO,EAAM,SAAS,MAAM,EAC5B,EAAS,EAAM,SAAS,QAAQ,EAChC,EAAU,EAAM,SAAS,SAAS,EAMxC,OAAO,EAAiB,MAAO,EAAgB,CAC7C,MAAA,CAJA,OACA,iBAHuB,EAAM,SAAS,kBAGvB,CAGX,EACJ,IAAK,CAAC,EAAM,QAAQ,YAAa,CAAY,EAC7C,uBAAwB,EACxB,MAAO,CAAC,CACN,KAAM,eACN,OAAQ,CAAC,EACT,MAAO,CACL,WAAY,OACZ,iBAAkB,MACpB,CACF,EAAG,CAAY,EACf,QAAS,GAAe,CAAC,CAC3B,CAAC,CACH,CAAC,QCzBY,EAA2B,EAAM,WAAW,SAAqB,EAAgB,EAAc,CAC1G,GAAM,CACJ,SACA,YACA,QACA,aACA,eACA,GAAG,GACD,EACE,EAAQ,EAAqB,EAC7B,EAAuB,EAAM,SAAS,sBAAsB,EAC5D,EAA0B,EAAM,SAAS,yBAAyB,EAClE,EAAsB,EAAM,SAAS,qBAAqB,EAC1D,EAAiB,EAAM,SAAS,YAAY,EAC5C,EAAQ,EAAM,SAAS,OAAO,EAC9B,EAAU,EAAM,SAAS,SAAS,EAClC,EAAS,EAAM,SAAS,QAAQ,EAChC,EAAwB,EAAM,SAAS,uBAAuB,EAC9D,EAAO,EAAM,SAAS,MAAM,EAC5B,EAAa,EAAM,SAAS,YAAY,EACxC,EAAiB,EAAM,SAAS,gBAAgB,EAChD,EAAmB,EAAM,SAAS,kBAAkB,EACpD,EAAO,EAAM,SAAS,MAAM,EAC5B,EAAa,EAAoB,SAAS,YAAY,EAC5D,EAAuB,EACvB,EAAsB,CACpB,OACA,IAAK,EAAM,QAAQ,SACnB,YAAa,CACP,GACF,EAAM,QAAQ,uBAAuB,EAAI,CAE7C,CACF,CAAC,EACD,IAAM,EAAuB,IAAiB,IAAA,GAAY,EAA0B,EAAM,QAAQ,QAAQ,EAAI,EACxG,EAAmB,EAAwB,EAC3C,EAAkB,EAAM,eAAe,cAAc,EAOrD,EAAU,EAAiB,MAAO,EAAgB,CACtD,MAAA,CANA,OACA,SACA,mBACA,kBAGI,EACJ,MAAO,CAAC,EAAgB,CACtB,GAAI,EACJ,kBAAmB,EACnB,mBAAoB,EACpB,OACA,GAAG,EACH,OAAQ,CAAC,EACT,UAAU,EAAO,CACX,EAAe,IAAI,EAAM,GAAG,GAC9B,EAAM,gBAAgB,CAE1B,EACA,MAAO,CACL,mBAAoB,CACtB,CACF,EAAG,CAAY,EACf,IAAK,CAAC,EAAc,EAAM,QAAQ,SAAU,CAAe,EAC3D,uBAAwB,CAC1B,CAAC,EACD,OAAoB,EAAA,EAAA,IAAA,CAAK,EAAsB,CAC7C,QAAS,EACT,oBAAqB,EACrB,SAAU,CAAC,EACX,gBAAiB,CAAC,EAClB,aAAc,EACd,YAAa,EACb,MAAO,IAAU,GACjB,aAAc,QACd,SAAU,CACZ,CAAC,CACH,CAAC,ECpFD,SAAgB,EAAW,EAAO,CAChC,OAAO,EAAoB,SAAU,CAAK,CAC5C,CCRA,IAAM,EAAmB,EAAoBA,EAAiB,sCAAsC,ECA9F,EAAgB,EAAoBC,EAAc,iBAAiB,ECAnE,EAAsB,EAAoBC,EAAoB,uBAAuB,ECD3F,SAAS,GAAqB,CAC7B,OAAOC,EAAoB,CAC5B,CCAA,SAAS,EAAc,CAAE,YAAW,YAAY,SAAU,GAAG,GAAS,CACrE,OAAuB,EAAA,EAAA,IAAA,CAAIC,EAAc,CACxC,GAAG,EACH,UAAW,EAAwB,0BAA2B,CAAS,EACvE,iBAAkB,CACnB,CAAC,CACF,CCPA,IAAM,EAAiB,EAAoBC,CAAa,ECAxD,SAAS,EAAa,EAAO,CAC5B,OAAuB,EAAA,EAAA,IAAA,CAAIC,EAAa,CAAE,GAAG,CAAM,CAAC,CACrD,CCFA,IAAM,EAAgB,EAAoBC,EAAc,iBAAiB,ECAnE,EAAkB,EAAoBC,EAAgB,mBAAmB,EEOzE,EAAW,CAChB,SAAU,EACV,MAAO,EACP,YAAa,EACb,MAAO,EACP,OAAQ,EACR,KAAM,EACN,SDdwB,EAAoBC,EAAiB,sCCcnD,EACV,MAAO,EACP,QAAS,EACT,aAAc,CACf,ECOM,EAAc,CAClB,GAAI,+DACJ,GAAI,+DACJ,GAAI,8DACN,EAEM,EAAmD,CACvD,QAAS,+CACT,YAAa,iDACf,EAEA,SAAgB,EAAM,CACpB,SACA,UACA,QACA,cACA,WACA,OAAO,KACP,eAAe,GACf,KAAM,EACN,UAAU,WACG,CAiBb,OAhBA,EAAA,EAAA,UAAA,KAAgB,CAEd,GADI,CAAC,GACD,EAAc,OAClB,IAAM,EAAiB,GAAqB,CACtC,EAAE,MAAQ,UACZ,EAAQ,CAEZ,EAEA,OADA,SAAS,iBAAiB,UAAW,CAAa,MACrC,SAAS,oBAAoB,UAAW,CAAa,CACpE,EAAG,CAAC,EAAQ,EAAc,CAAO,CAAC,EAE7B,GAKH,EAAA,EAAA,IAAA,CAAC,EAAS,KAAV,CACE,YAAA,GACA,wBAAyB,EACzB,aAAe,GAAS,CAClB,CAAC,GAAQ,CAAC,GACZ,EAAQ,CAEZ,YAEA,EAAA,EAAA,IAAA,CAAC,EAAS,OAAV,CAAA,UACE,EAAA,EAAA,KAAA,CAAC,EAAS,SAAV,CAAA,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,EAAS,SAAV,CAAoB,CAAA,GAMpB,EAAA,EAAA,KAAA,CAAC,EAAS,MAAV,CAAgB,UAAW,EAAY,YAAvC,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,4CAAf,CACG,IACC,EAAA,EAAA,IAAA,CAAC,MAAD,CACE,UAAW,qFAAqF,EAAmB,eAEnH,EAAA,EAAA,IAAA,CAAC,EAAD,CAAe,cAAA,GAAY,UAAU,kBAAoB,CAAA,CACtD,CAAA,GAEP,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,yDAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAS,MAAV,CAAA,SAAiB,CAAsB,CAAA,EACtC,IACC,EAAA,EAAA,IAAA,CAAC,EAAS,YAAV,CAAsB,UAAU,qDAC7B,CACmB,CAAA,CAErB,IACJ,CAAC,IACA,EAAA,EAAA,IAAA,CAAC,EAAS,MAAV,CACE,aAAW,QACX,UAAU,WACV,cAAY,wBAEZ,EAAA,EAAA,IAAA,CAAC,EAAD,CAAO,cAAA,GAAY,UAAU,kBAAoB,CAAA,CACnC,CAAA,CAEf,IACJ,CACa,GACC,CAAA,CAAA,CACJ,CAAA,CACJ,CAAA,EApDR,IAsDX,CAMA,SAAgB,EAAa,CAAE,YAA+B,CAC5D,OACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,kDACZ,UACE,CAAA,CAET"}
|
|
1
|
+
{"version":3,"file":"modal-CwoBUXp3.js","names":["Dialog.Backdrop","Dialog.Close","Dialog.Description","Dialog.createHandle","Dialog.Popup","Dialog.Portal","Dialog.Root","Dialog.Title","Dialog.Trigger","Dialog.Viewport"],"sources":["../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/dialog/backdrop/DialogBackdrop.mjs","../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/dialog/popup/DialogPopup.mjs","../../../../node_modules/.pnpm/@base-ui+react@1.7.0_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7/node_modules/@base-ui/react/dialog/root/DialogRoot.mjs","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-backdrop.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-close.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-description.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-handle.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-popup.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-portal.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-root.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-title.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-trigger.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/dialog-viewport.js","../../../../node_modules/.pnpm/@tinyrack+ui@0.27.1_@types+react@19.2.17_react-dom@19.2.7_react@19.2.7__react@19.2.7_shiki@4.4.2_tailwindcss@4.3.2/node_modules/@tinyrack/ui/dist/components/dialog/index.js","../../../frontend/src/components/ui/modal.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { useDialogRootContext } from \"../root/DialogRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\nimport { popupTransitionStateMapping } from \"../../utils/popupStateMapping.mjs\";\n\n/**\n * An overlay displayed beneath the popup.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport const DialogBackdrop = /*#__PURE__*/React.forwardRef(function DialogBackdrop(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n forceRender = false,\n ...elementProps\n } = componentProps;\n const store = useDialogRootContext();\n const open = store.useState('open');\n const nested = store.useState('nested');\n const mounted = store.useState('mounted');\n const transitionStatus = store.useState('transitionStatus');\n const state = {\n open,\n transitionStatus\n };\n return useRenderElement('div', componentProps, {\n state,\n ref: [store.context.backdropRef, forwardedRef],\n stateAttributesMapping: popupTransitionStateMapping,\n props: [{\n role: 'presentation',\n hidden: !mounted,\n style: {\n userSelect: 'none',\n WebkitUserSelect: 'none'\n }\n }, elementProps],\n enabled: forceRender || !nested\n });\n});\nif (process.env.NODE_ENV !== \"production\") DialogBackdrop.displayName = \"DialogBackdrop\";","'use client';\n\nimport * as React from 'react';\nimport { FloatingFocusManager } from \"../../floating-ui-react/index.mjs\";\nimport { useDialogRootContext } from \"../root/DialogRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\nimport { useDialogPortalContext } from \"../portal/DialogPortalContext.mjs\";\nimport { useOpenChangeComplete } from \"../../internals/useOpenChangeComplete.mjs\";\nimport { COMPOSITE_KEYS } from \"../../internals/composite/composite.mjs\";\nimport { FOCUSABLE_POPUP_PROPS, createDefaultInitialFocus } from \"../../utils/popups/index.mjs\";\nimport { dialogStateAttributesMapping } from \"../utils/stateAttributesMapping.mjs\";\n\n/**\n * A container for the dialog contents.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nimport { jsx as _jsx } from \"react/jsx-runtime\";\nexport const DialogPopup = /*#__PURE__*/React.forwardRef(function DialogPopup(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n finalFocus,\n initialFocus,\n ...elementProps\n } = componentProps;\n const store = useDialogRootContext();\n const descriptionElementId = store.useState('descriptionElementId');\n const disablePointerDismissal = store.useState('disablePointerDismissal');\n const floatingRootContext = store.useState('floatingRootContext');\n const rootPopupProps = store.useState('popupProps');\n const modal = store.useState('modal');\n const mounted = store.useState('mounted');\n const nested = store.useState('nested');\n const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');\n const open = store.useState('open');\n const openMethod = store.useState('openMethod');\n const titleElementId = store.useState('titleElementId');\n const transitionStatus = store.useState('transitionStatus');\n const role = store.useState('role');\n const floatingId = floatingRootContext.useState('floatingId');\n useDialogPortalContext();\n useOpenChangeComplete({\n open,\n ref: store.context.popupRef,\n onComplete() {\n if (open) {\n store.context.onOpenChangeComplete?.(true);\n }\n }\n });\n const resolvedInitialFocus = initialFocus === undefined ? createDefaultInitialFocus(store.context.popupRef) : initialFocus;\n const nestedDialogOpen = nestedOpenDialogCount > 0;\n const setPopupElement = store.useStateSetter('popupElement');\n const state = {\n open,\n nested,\n transitionStatus,\n nestedDialogOpen\n };\n const element = useRenderElement('div', componentProps, {\n state,\n props: [rootPopupProps, {\n id: floatingId,\n 'aria-labelledby': titleElementId,\n 'aria-describedby': descriptionElementId,\n role,\n ...FOCUSABLE_POPUP_PROPS,\n hidden: !mounted,\n onKeyDown(event) {\n if (COMPOSITE_KEYS.has(event.key)) {\n event.stopPropagation();\n }\n },\n style: {\n '--nested-dialogs': nestedOpenDialogCount\n }\n }, elementProps],\n ref: [forwardedRef, store.context.popupRef, setPopupElement],\n stateAttributesMapping: dialogStateAttributesMapping\n });\n return /*#__PURE__*/_jsx(FloatingFocusManager, {\n context: floatingRootContext,\n openInteractionType: openMethod,\n disabled: !mounted,\n closeOnFocusOut: !disablePointerDismissal,\n initialFocus: resolvedInitialFocus,\n returnFocus: finalFocus,\n modal: modal !== false,\n restoreFocus: \"popup\",\n children: element\n });\n});\nif (process.env.NODE_ENV !== \"production\") DialogPopup.displayName = \"DialogPopup\";","'use client';\n\nimport { useRenderDialogRoot } from \"./useRenderDialogRoot.mjs\";\n\n/**\n * Groups all parts of the dialog.\n * Doesn't render its own HTML element.\n *\n * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)\n */\nexport function DialogRoot(props) {\n return useRenderDialogRoot('dialog', props);\n}","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-backdrop.tsx\nconst TRDialogBackdrop = createComponentPart(Dialog.Backdrop, \"tr-layer-backdrop tr-dialog-backdrop\");\n//#endregion\nexport { TRDialogBackdrop };\n\n//# sourceMappingURL=dialog-backdrop.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-close.tsx\nconst TRDialogClose = createComponentPart(Dialog.Close, \"tr-dialog-close\");\n//#endregion\nexport { TRDialogClose };\n\n//# sourceMappingURL=dialog-close.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-description.tsx\nconst TRDialogDescription = createComponentPart(Dialog.Description, \"tr-dialog-description\");\n//#endregion\nexport { TRDialogDescription };\n\n//# sourceMappingURL=dialog-description.js.map","\"use client\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-handle.ts\nfunction createDialogHandle() {\n\treturn Dialog.createHandle();\n}\n//#endregion\nexport { createDialogHandle };\n\n//# sourceMappingURL=dialog-handle.js.map","\"use client\";\nimport { mergeComponentClassName } from \"../../internal/component-class-name.js\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-popup.tsx\nfunction TRDialogPopup({ className, placement = \"middle\", ...props }) {\n\treturn /* @__PURE__ */ jsx(Dialog.Popup, {\n\t\t...props,\n\t\tclassName: mergeComponentClassName(\"tr-dialog tr-dialog-box\", className),\n\t\t\"data-placement\": placement\n\t});\n}\n//#endregion\nexport { TRDialogPopup };\n\n//# sourceMappingURL=dialog-popup.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-portal.tsx\nconst TRDialogPortal = createComponentPart(Dialog.Portal);\n//#endregion\nexport { TRDialogPortal };\n\n//# sourceMappingURL=dialog-portal.js.map","\"use client\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-root.tsx\nfunction TRDialogRoot(props) {\n\treturn /* @__PURE__ */ jsx(Dialog.Root, { ...props });\n}\n//#endregion\nexport { TRDialogRoot };\n\n//# sourceMappingURL=dialog-root.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-title.tsx\nconst TRDialogTitle = createComponentPart(Dialog.Title, \"tr-dialog-title\");\n//#endregion\nexport { TRDialogTitle };\n\n//# sourceMappingURL=dialog-title.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-trigger.tsx\nconst TRDialogTrigger = createComponentPart(Dialog.Trigger, \"tr-dialog-trigger\");\n//#endregion\nexport { TRDialogTrigger };\n\n//# sourceMappingURL=dialog-trigger.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Dialog } from \"@base-ui/react/dialog\";\n//#region src/components/dialog/dialog-viewport.tsx\nconst TRDialogViewport = createComponentPart(Dialog.Viewport, \"tr-layer-viewport tr-dialog-viewport\");\n//#endregion\nexport { TRDialogViewport };\n\n//# sourceMappingURL=dialog-viewport.js.map","import { TRDialogBackdrop } from \"./dialog-backdrop.js\";\nimport { TRDialogClose } from \"./dialog-close.js\";\nimport { TRDialogDescription } from \"./dialog-description.js\";\nimport { createDialogHandle } from \"./dialog-handle.js\";\nimport { TRDialogPopup } from \"./dialog-popup.js\";\nimport { TRDialogPortal } from \"./dialog-portal.js\";\nimport { TRDialogRoot } from \"./dialog-root.js\";\nimport { TRDialogTitle } from \"./dialog-title.js\";\nimport { TRDialogTrigger } from \"./dialog-trigger.js\";\nimport { TRDialogViewport } from \"./dialog-viewport.js\";\n//#region src/components/dialog/index.tsx\nconst TRDialog = {\n\tBackdrop: TRDialogBackdrop,\n\tClose: TRDialogClose,\n\tDescription: TRDialogDescription,\n\tPopup: TRDialogPopup,\n\tPortal: TRDialogPortal,\n\tRoot: TRDialogRoot,\n\tViewport: TRDialogViewport,\n\tTitle: TRDialogTitle,\n\tTrigger: TRDialogTrigger,\n\tcreateHandle: createDialogHandle\n};\n//#endregion\nexport { TRDialog, TRDialogBackdrop, TRDialogClose, TRDialogDescription, TRDialogPopup, TRDialogPortal, TRDialogRoot, TRDialogTitle, TRDialogTrigger, TRDialogViewport, createDialogHandle };\n\n//# sourceMappingURL=index.js.map","import { TRDialog } from '@tinyrack/ui/components/dialog';\nimport type { LucideIcon } from 'lucide-react';\nimport { XIcon } from 'lucide-react';\nimport { type ReactNode, useEffect } from 'react';\n\ntype ModalVariant = 'default' | 'destructive';\n\ninterface ModalProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description?: string;\n children: ReactNode;\n size?: 'sm' | 'md' | 'lg';\n preventClose?: boolean;\n icon?: LucideIcon;\n variant?: ModalVariant;\n}\n\n/*\n Set through `--tr-dialog-box-max-width`, the override hook `.tr-dialog-box`\n exposes, rather than a `max-w-*` utility. The box feeds that same custom\n property into its own `width: min(…)` calculation, so a utility would move\n the max-width while leaving the width computation on the old value.\n\n This is the current design system overlay scale (20/32rem), not Tailwind's\n width scale. The local `lg` compatibility option now resolves to the largest\n supported overlay token, and `md` remains the unstyled `TRDialog` default.\n*/\nconst sizeClasses = {\n sm: '[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-sm)]',\n md: '[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-md)]',\n lg: '[--tr-dialog-box-max-width:var(--tinyrack-overlay-width-md)]',\n} as const;\n\nconst iconVariantClasses: Record<ModalVariant, string> = {\n default: 'bg-tinyrack-surface-muted text-tinyrack-text',\n destructive: 'bg-tinyrack-danger-surface text-tinyrack-danger-foreground',\n};\n\nexport function Modal({\n isOpen,\n onClose,\n title,\n description,\n children,\n size = 'md',\n preventClose = false,\n icon: IconComponent,\n variant = 'default',\n}: ModalProps) {\n useEffect(() => {\n if (!isOpen) return;\n if (preventClose) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n onClose();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, preventClose, onClose]);\n\n if (!isOpen) {\n return null;\n }\n\n return (\n <TRDialog.Root\n defaultOpen\n disablePointerDismissal={preventClose}\n onOpenChange={(open) => {\n if (!open && !preventClose) {\n onClose();\n }\n }}\n >\n <TRDialog.Portal>\n <TRDialog.Viewport>\n <TRDialog.Backdrop />\n {/*\n No max-height here: `.tr-dialog-box` already clamps itself against\n `100dvh` minus the viewport gap, which is more correct than the\n `90vh`/`85vh` this used to override it with.\n */}\n <TRDialog.Popup className={sizeClasses[size]}>\n <div className=\"flex items-start gap-tinyrack-md\">\n {IconComponent && (\n <div\n className={`flex size-tinyrack-2xl shrink-0 items-center justify-center rounded-tinyrack-full ${iconVariantClasses[variant]}`}\n >\n <IconComponent aria-hidden className=\"size-tinyrack-lg\" />\n </div>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-tinyrack-3xs\">\n <TRDialog.Title>{title}</TRDialog.Title>\n {description && (\n <TRDialog.Description className=\"text-tinyrack-text-muted text-tinyrack-xs\">\n {description}\n </TRDialog.Description>\n )}\n </div>\n {!preventClose && (\n <TRDialog.Close\n aria-label=\"Close\"\n className=\"shrink-0\"\n data-testid=\"modal-close\"\n >\n <XIcon aria-hidden className=\"size-tinyrack-lg\" />\n </TRDialog.Close>\n )}\n </div>\n {children}\n </TRDialog.Popup>\n </TRDialog.Viewport>\n </TRDialog.Portal>\n </TRDialog.Root>\n );\n}\n\ninterface ModalActionsProps {\n children: ReactNode;\n}\n\nexport function ModalActions({ children }: ModalActionsProps) {\n return (\n <div className=\"flex justify-end gap-tinyrack-sm pt-tinyrack-lg\">\n {children}\n </div>\n );\n}\n"],"x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"mappings":"4VAaa,EAA8B,EAAM,WAAW,SAAwB,EAAgB,EAAc,CAChH,GAAM,CACJ,SACA,YACA,QACA,cAAc,GACd,GAAG,GACD,EACE,EAAQ,EAAqB,EAC7B,EAAO,EAAM,SAAS,MAAM,EAC5B,EAAS,EAAM,SAAS,QAAQ,EAChC,EAAU,EAAM,SAAS,SAAS,EAMxC,OAAO,EAAiB,MAAO,EAAgB,CAC7C,MAAA,CAJA,OACA,iBAHuB,EAAM,SAAS,kBAGvB,CAGX,EACJ,IAAK,CAAC,EAAM,QAAQ,YAAa,CAAY,EAC7C,uBAAwB,EACxB,MAAO,CAAC,CACN,KAAM,eACN,OAAQ,CAAC,EACT,MAAO,CACL,WAAY,OACZ,iBAAkB,MACpB,CACF,EAAG,CAAY,EACf,QAAS,GAAe,CAAC,CAC3B,CAAC,CACH,CAAC,QCzBY,EAA2B,EAAM,WAAW,SAAqB,EAAgB,EAAc,CAC1G,GAAM,CACJ,SACA,YACA,QACA,aACA,eACA,GAAG,GACD,EACE,EAAQ,EAAqB,EAC7B,EAAuB,EAAM,SAAS,sBAAsB,EAC5D,EAA0B,EAAM,SAAS,yBAAyB,EAClE,EAAsB,EAAM,SAAS,qBAAqB,EAC1D,EAAiB,EAAM,SAAS,YAAY,EAC5C,EAAQ,EAAM,SAAS,OAAO,EAC9B,EAAU,EAAM,SAAS,SAAS,EAClC,EAAS,EAAM,SAAS,QAAQ,EAChC,EAAwB,EAAM,SAAS,uBAAuB,EAC9D,EAAO,EAAM,SAAS,MAAM,EAC5B,EAAa,EAAM,SAAS,YAAY,EACxC,EAAiB,EAAM,SAAS,gBAAgB,EAChD,EAAmB,EAAM,SAAS,kBAAkB,EACpD,EAAO,EAAM,SAAS,MAAM,EAC5B,EAAa,EAAoB,SAAS,YAAY,EAC5D,EAAuB,EACvB,EAAsB,CACpB,OACA,IAAK,EAAM,QAAQ,SACnB,YAAa,CACP,GACF,EAAM,QAAQ,uBAAuB,EAAI,CAE7C,CACF,CAAC,EACD,IAAM,EAAuB,IAAiB,IAAA,GAAY,EAA0B,EAAM,QAAQ,QAAQ,EAAI,EACxG,EAAmB,EAAwB,EAC3C,EAAkB,EAAM,eAAe,cAAc,EAOrD,EAAU,EAAiB,MAAO,EAAgB,CACtD,MAAA,CANA,OACA,SACA,mBACA,kBAGI,EACJ,MAAO,CAAC,EAAgB,CACtB,GAAI,EACJ,kBAAmB,EACnB,mBAAoB,EACpB,OACA,GAAG,EACH,OAAQ,CAAC,EACT,UAAU,EAAO,CACX,EAAe,IAAI,EAAM,GAAG,GAC9B,EAAM,gBAAgB,CAE1B,EACA,MAAO,CACL,mBAAoB,CACtB,CACF,EAAG,CAAY,EACf,IAAK,CAAC,EAAc,EAAM,QAAQ,SAAU,CAAe,EAC3D,uBAAwB,CAC1B,CAAC,EACD,OAAoB,EAAA,EAAA,IAAA,CAAK,EAAsB,CAC7C,QAAS,EACT,oBAAqB,EACrB,SAAU,CAAC,EACX,gBAAiB,CAAC,EAClB,aAAc,EACd,YAAa,EACb,MAAO,IAAU,GACjB,aAAc,QACd,SAAU,CACZ,CAAC,CACH,CAAC,ECpFD,SAAgB,EAAW,EAAO,CAChC,OAAO,EAAoB,SAAU,CAAK,CAC5C,CCRA,IAAM,EAAmB,EAAoBA,EAAiB,sCAAsC,ECA9F,EAAgB,EAAoBC,EAAc,iBAAiB,ECAnE,EAAsB,EAAoBC,EAAoB,uBAAuB,ECD3F,SAAS,GAAqB,CAC7B,OAAOC,EAAoB,CAC5B,CCAA,SAAS,EAAc,CAAE,YAAW,YAAY,SAAU,GAAG,GAAS,CACrE,OAAuB,EAAA,EAAA,IAAA,CAAIC,EAAc,CACxC,GAAG,EACH,UAAW,EAAwB,0BAA2B,CAAS,EACvE,iBAAkB,CACnB,CAAC,CACF,CCPA,IAAM,EAAiB,EAAoBC,CAAa,ECAxD,SAAS,EAAa,EAAO,CAC5B,OAAuB,EAAA,EAAA,IAAA,CAAIC,EAAa,CAAE,GAAG,CAAM,CAAC,CACrD,CCFA,IAAM,EAAgB,EAAoBC,EAAc,iBAAiB,ECAnE,EAAkB,EAAoBC,EAAgB,mBAAmB,EEOzE,EAAW,CAChB,SAAU,EACV,MAAO,EACP,YAAa,EACb,MAAO,EACP,OAAQ,EACR,KAAM,EACN,SDdwB,EAAoBC,EAAiB,sCCcnD,EACV,MAAO,EACP,QAAS,EACT,aAAc,CACf,ECOM,EAAc,CAClB,GAAI,+DACJ,GAAI,+DACJ,GAAI,8DACN,EAEM,EAAmD,CACvD,QAAS,+CACT,YAAa,4DACf,EAEA,SAAgB,EAAM,CACpB,SACA,UACA,QACA,cACA,WACA,OAAO,KACP,eAAe,GACf,KAAM,EACN,UAAU,WACG,CAiBb,OAhBA,EAAA,EAAA,UAAA,KAAgB,CAEd,GADI,CAAC,GACD,EAAc,OAClB,IAAM,EAAiB,GAAqB,CACtC,EAAE,MAAQ,UACZ,EAAQ,CAEZ,EAEA,OADA,SAAS,iBAAiB,UAAW,CAAa,MACrC,SAAS,oBAAoB,UAAW,CAAa,CACpE,EAAG,CAAC,EAAQ,EAAc,CAAO,CAAC,EAE7B,GAKH,EAAA,EAAA,IAAA,CAAC,EAAS,KAAV,CACE,YAAA,GACA,wBAAyB,EACzB,aAAe,GAAS,CAClB,CAAC,GAAQ,CAAC,GACZ,EAAQ,CAEZ,YAEA,EAAA,EAAA,IAAA,CAAC,EAAS,OAAV,CAAA,UACE,EAAA,EAAA,KAAA,CAAC,EAAS,SAAV,CAAA,SAAA,EACE,EAAA,EAAA,IAAA,CAAC,EAAS,SAAV,CAAoB,CAAA,GAMpB,EAAA,EAAA,KAAA,CAAC,EAAS,MAAV,CAAgB,UAAW,EAAY,YAAvC,EACE,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,4CAAf,CACG,IACC,EAAA,EAAA,IAAA,CAAC,MAAD,CACE,UAAW,qFAAqF,EAAmB,eAEnH,EAAA,EAAA,IAAA,CAAC,EAAD,CAAe,cAAA,GAAY,UAAU,kBAAoB,CAAA,CACtD,CAAA,GAEP,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,yDAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAS,MAAV,CAAA,SAAiB,CAAsB,CAAA,EACtC,IACC,EAAA,EAAA,IAAA,CAAC,EAAS,YAAV,CAAsB,UAAU,qDAC7B,CACmB,CAAA,CAErB,IACJ,CAAC,IACA,EAAA,EAAA,IAAA,CAAC,EAAS,MAAV,CACE,aAAW,QACX,UAAU,WACV,cAAY,wBAEZ,EAAA,EAAA,IAAA,CAAC,EAAD,CAAO,cAAA,GAAY,UAAU,kBAAoB,CAAA,CACnC,CAAA,CAEf,IACJ,CACa,GACC,CAAA,CAAA,CACJ,CAAA,CACJ,CAAA,EApDR,IAsDX,CAMA,SAAgB,EAAa,CAAE,YAA+B,CAC5D,OACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,kDACZ,UACE,CAAA,CAET"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{J as e,U as t,Z as n,i as r,q as i,s as a}from"./createLucideIcon-BPpAIXR_.js";import{On as o,Sn as s,Sr as c,i as l,n as u,r as d,ur as f}from"./config-DTQCUlfZ.js";import{t as p}from"./useMutation-BWZ0G4Td.js";import{t as m}from"./useQuery-
|
|
2
|
-
//# sourceMappingURL=passkey-
|
|
1
|
+
import{J as e,U as t,Z as n,i as r,q as i,s as a}from"./createLucideIcon-BPpAIXR_.js";import{On as o,Sn as s,Sr as c,i as l,n as u,r as d,ur as f}from"./config-DTQCUlfZ.js";import{t as p}from"./useMutation-BWZ0G4Td.js";import{t as m}from"./useQuery-CI5Ukuae.js";import{t as h}from"./fingerprint-pattern-DP62Vk8x.js";import{F as g,N as _,R as v,S as y,j as b,m as x,z as S}from"./index-H6CJKjiN.js";import{t as C}from"./auth-page-header-ZLhtQ7Et.js";import{t as w}from"./alert-leqyvHOp.js";import{t as T}from"./auth-layout-CcJy6dVY.js";import{t as E}from"./promise-OpBtq8tG.js";import{n as D,t as O}from"./auth-footer-Cz5ivsDc.js";import{t as k}from"./passkey-error-ZqqPQUta.js";import{t as A}from"./passkey-J1qnYp9A.js";var j=n(e()),M=f({queryKey:u.pendingSecondFactorMethods(),queryFn:async()=>l(await d.api.auth[`2fa`].methods.$get()),retry:!1}),N=i();function P(){let{t:e}=r(),n=o(),i=c(),l=x.useSearch(),[u,d]=(0,j.useState)(null),f=(0,j.useRef)(!1),P=m({...M,enabled:u!==null}).data?.methods.includes(`totp`)??!1,F=t=>{switch(t){case`unsupported`:return e(`verifyPasskey.error.unsupported`);case`not_allowed`:return e(`verifyPasskey.error.notAllowed`);case`expired`:return e(`verifyPasskey.error.expired`);case`user_mismatch`:return e(`verifyPasskey.error.userMismatch`);case`verification_failed`:return e(`verifyPasskey.error.failed`)}},I=p({...A,onSuccess:async e=>{e.user&&(i.setQueryData(y.queryKey,{user:e.user}),await E(),g(l)?window.location.href=b(l):n.navigate({to:`/profile`}))},onError:async e=>{let t=k(e);d({message:F(t),reason:t})},onSettled:()=>{i.invalidateQueries({queryKey:y.queryKey})}});return(0,j.useEffect)(()=>{f.current||(f.current=!0,I.mutate())},[I]),(0,N.jsxs)(T,{children:[(0,N.jsx)(C,{align:`center`,subtitle:e(`verifyPasskey.subtitle`),title:e(`verifyPasskey.title`)}),(0,N.jsxs)(`div`,{className:`flex flex-col items-center gap-tinyrack-lg`,children:[(0,N.jsx)(`div`,{className:`flex size-tinyrack-3xl items-center justify-center rounded-tinyrack-full bg-tinyrack-surface-muted`,children:(0,N.jsx)(h,{"aria-hidden":!0,className:`size-tinyrack-2xl text-tinyrack-text-muted`})}),I.isPending&&(0,N.jsxs)(`div`,{className:`flex flex-col items-center gap-tinyrack-sm`,children:[(0,N.jsx)(t,{uiSize:`md`}),(0,N.jsx)(`p`,{className:`text-center text-tinyrack-sm text-tinyrack-text-muted`,children:e(`verifyPasskey.waiting`)})]}),u&&(0,N.jsxs)(`div`,{className:`flex w-full flex-col items-center gap-tinyrack-md`,children:[(0,N.jsx)(w,{className:`w-full`,icon:v,type:`error`,children:u.message}),P&&(0,N.jsx)(S,{className:`w-full`,intent:`primary`,render:(0,N.jsx)(s,{search:_(l),to:`/verify/totp`}),uiSize:`lg`,children:e(`verifyPasskey.useTotp`)}),(0,N.jsx)(a,{appearance:P?`outline`:`solid`,className:`w-full`,intent:P?`neutral`:`primary`,onClick:()=>{d(null),I.mutate()},type:`button`,uiSize:`lg`,children:e(`verifyPasskey.retry`)})]})]}),(0,N.jsx)(O,{children:(0,N.jsx)(D,{link:(0,N.jsx)(s,{search:l,to:`/login`,children:e(`verifyPasskey.backToLogin`)})})})]})}export{P as component};
|
|
2
|
+
//# sourceMappingURL=passkey-Jw2CRzJp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-CynCz1Dt.js","names":["useMutation","useQuery","useQueryClient","Link","useRouter","TRButton","TRLinkButton","TRSpinner","CircleAlertIcon","FingerprintIcon","useEffect","useRef","useState","useTranslation","AuthFooter","AuthFooterLink","AuthPageHeader","Alert","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","classifyPasskeyError","tick","authenticateWithPasskeyMutationOptions","getPendingSecondFactorMethodsQueryOptions","getSessionQueryOptions","Route","VerifyPasskey","t","router","queryClient","search","useSearch","error","setError","message","reason","PasskeyErrorReason","hasStarted","pendingMethodsQuery","enabled","canUseTotp","data","methods","includes","getErrorMessage","verifyMutation","onSuccess","user","setQueryData","queryKey","window","location","href","navigate","to","onError","err","onSettled","invalidateQueries","current","mutate","isPending","component"],"sources":["../../../frontend/src/queries/second-factor.ts","../../../frontend/src/routes/verify/passkey/index.tsx?tsr-split=component"],"sourcesContent":["import { queryOptions } from '@tanstack/react-query';\nimport type { InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\nimport { queryKeys } from './keys';\n\nexport type PendingSecondFactorMethodsResponse = InferResponseType<\n (typeof client.api.auth)['2fa']['methods']['$get'],\n 200\n>;\n\nexport const getPendingSecondFactorMethodsQueryOptions = queryOptions({\n queryKey: queryKeys.pendingSecondFactorMethods(),\n queryFn: async () => {\n const res = await client.api.auth['2fa'].methods.$get();\n return jsonOk(res);\n },\n retry: false,\n});\n","import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLinkButton } from '@tinyrack/ui/components/link-button';\nimport { TRSpinner } from '@tinyrack/ui/components/spinner';\nimport { CircleAlertIcon, FingerprintIcon } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport {\n classifyPasskeyError,\n type PasskeyErrorReason,\n} from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { authenticateWithPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport { getPendingSecondFactorMethodsQueryOptions } from '#frontend/queries/second-factor.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/verify/passkey/')({\n component: VerifyPasskey,\n validateSearch: SearchSchema,\n});\n\nfunction VerifyPasskey() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const [error, setError] = useState<{\n message: string;\n reason: PasskeyErrorReason;\n } | null>(null);\n const hasStarted = useRef(false);\n const pendingMethodsQuery = useQuery({\n ...getPendingSecondFactorMethodsQueryOptions,\n enabled: error !== null,\n });\n const canUseTotp =\n pendingMethodsQuery.data?.methods.includes('totp') ?? false;\n\n const getErrorMessage = (reason: PasskeyErrorReason) => {\n switch (reason) {\n case 'unsupported':\n return t('verifyPasskey.error.unsupported');\n case 'not_allowed':\n return t('verifyPasskey.error.notAllowed');\n case 'expired':\n return t('verifyPasskey.error.expired');\n case 'user_mismatch':\n return t('verifyPasskey.error.userMismatch');\n case 'verification_failed':\n return t('verifyPasskey.error.failed');\n }\n };\n\n const verifyMutation = useMutation({\n ...authenticateWithPasskeyMutationOptions,\n onSuccess: async (data) => {\n if (data.user) {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: data.user,\n });\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n },\n onError: async (err) => {\n const reason = classifyPasskeyError(err);\n setError({\n message: getErrorMessage(reason),\n reason,\n });\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n // Auto-start passkey authentication on mount (with guard for StrictMode)\n useEffect(() => {\n if (hasStarted.current) return;\n hasStarted.current = true;\n verifyMutation.mutate();\n }, [verifyMutation]);\n\n return (\n <AuthLayout>\n {/*\n Centred: this screen has no form to scan, only a device prompt to wait\n on, so the eye has nothing to follow down a left edge.\n */}\n <AuthPageHeader\n align=\"center\"\n subtitle={t('verifyPasskey.subtitle')}\n title={t('verifyPasskey.title')}\n />\n\n <div className=\"flex flex-col items-center gap-tinyrack-lg\">\n <div className=\"flex size-tinyrack-3xl items-center justify-center rounded-tinyrack-full bg-tinyrack-surface-muted\">\n <FingerprintIcon\n aria-hidden\n className=\"size-tinyrack-2xl text-tinyrack-text-muted\"\n />\n </div>\n\n {verifyMutation.isPending && (\n <div className=\"flex flex-col items-center gap-tinyrack-sm\">\n <TRSpinner uiSize=\"md\" />\n <p className=\"text-center text-tinyrack-sm text-tinyrack-text-muted\">\n {t('verifyPasskey.waiting')}\n </p>\n </div>\n )}\n\n {error && (\n <div className=\"flex w-full flex-col items-center gap-tinyrack-md\">\n <Alert className=\"w-full\" icon={CircleAlertIcon} type=\"error\">\n {error.message}\n </Alert>\n {canUseTotp && (\n <TRLinkButton\n className=\"w-full\"\n intent=\"primary\"\n render={\n <Link search={extractOAuthParams(search)} to=\"/verify/totp\" />\n }\n uiSize=\"lg\"\n >\n {t('verifyPasskey.useTotp')}\n </TRLinkButton>\n )}\n <TRButton\n appearance={canUseTotp ? 'outline' : 'solid'}\n className=\"w-full\"\n intent={canUseTotp ? 'neutral' : 'primary'}\n onClick={() => {\n setError(null);\n verifyMutation.mutate();\n }}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('verifyPasskey.retry')}\n </TRButton>\n </div>\n )}\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={search} to=\"/login\">\n {t('verifyPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"mappings":"6tBAUA,EAAA,EAAA,4GAOA,CAAA,QCoBA,SAAS4B,GAAgB,CACvB,GAAM,CAAEC,KAAMhB,EAAe,EACvBiB,EAAS1B,EAAU,EACnB2B,EAAc7B,EAAe,EAC7B8B,EAASL,EAAMM,UAAU,EACzB,CAACC,EAAOC,IAAAA,EAAAA,EAAAA,SAAAA,CAGJ,IAAI,EACRI,GAAAA,EAAAA,EAAAA,OAAAA,CAAoB,EAAK,EAKzBG,EAJsBzC,EAAS,CACnC,GAAGwB,EACHgB,QAASP,IAAU,IACrB,CAEEM,CAAAA,CAAoBG,MAAMC,QAAQC,SAAS,MAAM,GAAK,GAElDC,EAAmBT,GAA+B,CACtD,OAAQA,EAAR,CACE,IAAK,cACH,OAAOR,EAAE,iCAAiC,EAC5C,IAAK,cACH,OAAOA,EAAE,gCAAgC,EAC3C,IAAK,UACH,OAAOA,EAAE,6BAA6B,EACxC,IAAK,gBACH,OAAOA,EAAE,kCAAkC,EAC7C,IAAK,sBACH,OAAOA,EAAE,4BAA4B,CACzC,CACF,EAEMkB,EAAiB/C,EAAY,CACjC,GAAGwB,EACHwB,UAAW,KAAOL,IAAS,CACrBA,EAAKM,OACPlB,EAAYmB,aAAaxB,EAAuByB,SAAU,CACxDF,KAAMN,EAAKM,IACb,CAAC,EACD,MAAM1B,EAAK,EAEPF,EAAYW,CAAM,EACpBoB,OAAOC,SAASC,KAAOnC,EAA+Ba,CAAM,EAE5DF,EAAOyB,SAAS,CAAEC,GAAI,UAAW,CAAC,EAGxC,EACAC,QAAS,KAAOC,IAAQ,CACtB,IAAMrB,EAASf,EAAqBoC,CAAG,EACvCvB,EAAS,CACPC,QAASU,EAAgBT,CAAM,EAC/BA,QACF,CAAC,CACH,EACAsB,cAAiB,CACf5B,EAAY6B,kBAAkB,CAC5BT,SAAUzB,EAAuByB,QACnC,CAAC,CACH,CACF,CAAC,EASD,OANAzC,EAAAA,EAAAA,UAAAA,KAAgB,CACV6B,EAAWsB,UACftB,EAAWsB,QAAU,GACrBd,EAAee,OAAO,EACxB,EAAG,CAACf,CAAc,CAAC,GAGjB,EAAA,EAAA,KAAA,CAAC,EAAD,CAAA,SAAA,EAKE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MAAM,SACN,SAAUlB,EAAE,wBAAwB,EACpC,MAAOA,EAAE,qBAAqB,CAAE,CAAA,GAGlC,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,sDAAf,EACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,+GACb,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,cAAA,GACA,UAAU,4CAA4C,CAAA,CAErD,CAAA,EAEJkB,EAAegB,YACd,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,sDAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAW,OAAO,IAAI,CAAA,GACtB,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,iEACVlC,EAAE,uBAAuB,CACzB,CAAA,CACA,IAGNK,IACC,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,6DAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAO,UAAU,SAAS,KAAM1B,EAAiB,KAAK,iBACnD0B,EAAME,OACF,CAAA,EACNM,IACC,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,UAAU,SACV,OAAO,UACP,QACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAM,OAAQtB,EAAmBY,CAAM,EAAG,GAAG,cAAc,CAAA,EAE7D,OAAO,cAENH,EAAE,uBAAuB,CACd,CAAA,GAEhB,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,WAAYa,EAAa,UAAY,QACrC,UAAU,SACV,OAAQA,EAAa,UAAY,UACjC,YAAe,CACbP,EAAS,IAAI,EACbY,EAAee,OAAO,CACxB,EACA,KAAK,SACL,OAAO,cAENjC,EAAE,qBAAqB,CAChB,CAAA,CACP,GAEJ,KAEL,EAAA,EAAA,IAAA,CAAC,EAAD,CAAA,UACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAcG,SAAQ,GAAG,kBACtBH,EAAE,2BAA2B,CAC1B,CAAA,CACP,CAAA,CAEO,CAAA,CACF,CAAA,CAAA,CAEhB"}
|
|
1
|
+
{"version":3,"file":"passkey-Jw2CRzJp.js","names":["useMutation","useQuery","useQueryClient","Link","useRouter","TRButton","TRLinkButton","TRSpinner","CircleAlertIcon","FingerprintIcon","useEffect","useRef","useState","useTranslation","AuthFooter","AuthFooterLink","AuthPageHeader","Alert","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","classifyPasskeyError","tick","authenticateWithPasskeyMutationOptions","getPendingSecondFactorMethodsQueryOptions","getSessionQueryOptions","Route","VerifyPasskey","t","router","queryClient","search","useSearch","error","setError","message","reason","PasskeyErrorReason","hasStarted","pendingMethodsQuery","enabled","canUseTotp","data","methods","includes","getErrorMessage","verifyMutation","onSuccess","user","setQueryData","queryKey","window","location","href","navigate","to","onError","err","onSettled","invalidateQueries","current","mutate","isPending","component"],"sources":["../../../frontend/src/queries/second-factor.ts","../../../frontend/src/routes/verify/passkey/index.tsx?tsr-split=component"],"sourcesContent":["import { queryOptions } from '@tanstack/react-query';\nimport type { InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\nimport { queryKeys } from './keys';\n\nexport type PendingSecondFactorMethodsResponse = InferResponseType<\n (typeof client.api.auth)['2fa']['methods']['$get'],\n 200\n>;\n\nexport const getPendingSecondFactorMethodsQueryOptions = queryOptions({\n queryKey: queryKeys.pendingSecondFactorMethods(),\n queryFn: async () => {\n const res = await client.api.auth['2fa'].methods.$get();\n return jsonOk(res);\n },\n retry: false,\n});\n","import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLinkButton } from '@tinyrack/ui/components/link-button';\nimport { TRSpinner } from '@tinyrack/ui/components/spinner';\nimport { CircleAlertIcon, FingerprintIcon } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { Alert } from '#frontend/components/ui/alert.tsx';\nimport { AuthLayout } from '#frontend/features/layout/auth-layout.tsx';\nimport {\n buildAuthenticatedAuthorizeUrl,\n extractOAuthParams,\n isOAuthFlow,\n OAuthSearchSchema,\n} from '#frontend/libs/oauth-search.ts';\nimport {\n classifyPasskeyError,\n type PasskeyErrorReason,\n} from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { authenticateWithPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport { getPendingSecondFactorMethodsQueryOptions } from '#frontend/queries/second-factor.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/verify/passkey/')({\n component: VerifyPasskey,\n validateSearch: SearchSchema,\n});\n\nfunction VerifyPasskey() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const [error, setError] = useState<{\n message: string;\n reason: PasskeyErrorReason;\n } | null>(null);\n const hasStarted = useRef(false);\n const pendingMethodsQuery = useQuery({\n ...getPendingSecondFactorMethodsQueryOptions,\n enabled: error !== null,\n });\n const canUseTotp =\n pendingMethodsQuery.data?.methods.includes('totp') ?? false;\n\n const getErrorMessage = (reason: PasskeyErrorReason) => {\n switch (reason) {\n case 'unsupported':\n return t('verifyPasskey.error.unsupported');\n case 'not_allowed':\n return t('verifyPasskey.error.notAllowed');\n case 'expired':\n return t('verifyPasskey.error.expired');\n case 'user_mismatch':\n return t('verifyPasskey.error.userMismatch');\n case 'verification_failed':\n return t('verifyPasskey.error.failed');\n }\n };\n\n const verifyMutation = useMutation({\n ...authenticateWithPasskeyMutationOptions,\n onSuccess: async (data) => {\n if (data.user) {\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: data.user,\n });\n await tick();\n\n if (isOAuthFlow(search)) {\n window.location.href = buildAuthenticatedAuthorizeUrl(search);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n },\n onError: async (err) => {\n const reason = classifyPasskeyError(err);\n setError({\n message: getErrorMessage(reason),\n reason,\n });\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n // Auto-start passkey authentication on mount (with guard for StrictMode)\n useEffect(() => {\n if (hasStarted.current) return;\n hasStarted.current = true;\n verifyMutation.mutate();\n }, [verifyMutation]);\n\n return (\n <AuthLayout>\n {/*\n Centred: this screen has no form to scan, only a device prompt to wait\n on, so the eye has nothing to follow down a left edge.\n */}\n <AuthPageHeader\n align=\"center\"\n subtitle={t('verifyPasskey.subtitle')}\n title={t('verifyPasskey.title')}\n />\n\n <div className=\"flex flex-col items-center gap-tinyrack-lg\">\n <div className=\"flex size-tinyrack-3xl items-center justify-center rounded-tinyrack-full bg-tinyrack-surface-muted\">\n <FingerprintIcon\n aria-hidden\n className=\"size-tinyrack-2xl text-tinyrack-text-muted\"\n />\n </div>\n\n {verifyMutation.isPending && (\n <div className=\"flex flex-col items-center gap-tinyrack-sm\">\n <TRSpinner uiSize=\"md\" />\n <p className=\"text-center text-tinyrack-sm text-tinyrack-text-muted\">\n {t('verifyPasskey.waiting')}\n </p>\n </div>\n )}\n\n {error && (\n <div className=\"flex w-full flex-col items-center gap-tinyrack-md\">\n <Alert className=\"w-full\" icon={CircleAlertIcon} type=\"error\">\n {error.message}\n </Alert>\n {canUseTotp && (\n <TRLinkButton\n className=\"w-full\"\n intent=\"primary\"\n render={\n <Link search={extractOAuthParams(search)} to=\"/verify/totp\" />\n }\n uiSize=\"lg\"\n >\n {t('verifyPasskey.useTotp')}\n </TRLinkButton>\n )}\n <TRButton\n appearance={canUseTotp ? 'outline' : 'solid'}\n className=\"w-full\"\n intent={canUseTotp ? 'neutral' : 'primary'}\n onClick={() => {\n setError(null);\n verifyMutation.mutate();\n }}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('verifyPasskey.retry')}\n </TRButton>\n </div>\n )}\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={search} to=\"/login\">\n {t('verifyPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"mappings":"6tBAUA,EAAA,EAAA,4GAOA,CAAA,QCoBA,SAAS4B,GAAgB,CACvB,GAAM,CAAEC,KAAMhB,EAAe,EACvBiB,EAAS1B,EAAU,EACnB2B,EAAc7B,EAAe,EAC7B8B,EAASL,EAAMM,UAAU,EACzB,CAACC,EAAOC,IAAAA,EAAAA,EAAAA,SAAAA,CAGJ,IAAI,EACRI,GAAAA,EAAAA,EAAAA,OAAAA,CAAoB,EAAK,EAKzBG,EAJsBzC,EAAS,CACnC,GAAGwB,EACHgB,QAASP,IAAU,IACrB,CAEEM,CAAAA,CAAoBG,MAAMC,QAAQC,SAAS,MAAM,GAAK,GAElDC,EAAmBT,GAA+B,CACtD,OAAQA,EAAR,CACE,IAAK,cACH,OAAOR,EAAE,iCAAiC,EAC5C,IAAK,cACH,OAAOA,EAAE,gCAAgC,EAC3C,IAAK,UACH,OAAOA,EAAE,6BAA6B,EACxC,IAAK,gBACH,OAAOA,EAAE,kCAAkC,EAC7C,IAAK,sBACH,OAAOA,EAAE,4BAA4B,CACzC,CACF,EAEMkB,EAAiB/C,EAAY,CACjC,GAAGwB,EACHwB,UAAW,KAAOL,IAAS,CACrBA,EAAKM,OACPlB,EAAYmB,aAAaxB,EAAuByB,SAAU,CACxDF,KAAMN,EAAKM,IACb,CAAC,EACD,MAAM1B,EAAK,EAEPF,EAAYW,CAAM,EACpBoB,OAAOC,SAASC,KAAOnC,EAA+Ba,CAAM,EAE5DF,EAAOyB,SAAS,CAAEC,GAAI,UAAW,CAAC,EAGxC,EACAC,QAAS,KAAOC,IAAQ,CACtB,IAAMrB,EAASf,EAAqBoC,CAAG,EACvCvB,EAAS,CACPC,QAASU,EAAgBT,CAAM,EAC/BA,QACF,CAAC,CACH,EACAsB,cAAiB,CACf5B,EAAY6B,kBAAkB,CAC5BT,SAAUzB,EAAuByB,QACnC,CAAC,CACH,CACF,CAAC,EASD,OANAzC,EAAAA,EAAAA,UAAAA,KAAgB,CACV6B,EAAWsB,UACftB,EAAWsB,QAAU,GACrBd,EAAee,OAAO,EACxB,EAAG,CAACf,CAAc,CAAC,GAGjB,EAAA,EAAA,KAAA,CAAC,EAAD,CAAA,SAAA,EAKE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MAAM,SACN,SAAUlB,EAAE,wBAAwB,EACpC,MAAOA,EAAE,qBAAqB,CAAE,CAAA,GAGlC,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,sDAAf,EACE,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAU,+GACb,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,cAAA,GACA,UAAU,4CAA4C,CAAA,CAErD,CAAA,EAEJkB,EAAegB,YACd,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,sDAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAW,OAAO,IAAI,CAAA,GACtB,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAU,iEACVlC,EAAE,uBAAuB,CACzB,CAAA,CACA,IAGNK,IACC,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAU,6DAAf,EACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAO,UAAU,SAAS,KAAM1B,EAAiB,KAAK,iBACnD0B,EAAME,OACF,CAAA,EACNM,IACC,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,UAAU,SACV,OAAO,UACP,QACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAM,OAAQtB,EAAmBY,CAAM,EAAG,GAAG,cAAc,CAAA,EAE7D,OAAO,cAENH,EAAE,uBAAuB,CACd,CAAA,GAEhB,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,WAAYa,EAAa,UAAY,QACrC,UAAU,SACV,OAAQA,EAAa,UAAY,UACjC,YAAe,CACbP,EAAS,IAAI,EACbY,EAAee,OAAO,CACxB,EACA,KAAK,SACL,OAAO,cAENjC,EAAE,qBAAqB,CAChB,CAAA,CACP,GAEJ,KAEL,EAAA,EAAA,IAAA,CAAC,EAAD,CAAA,UACE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAcG,SAAQ,GAAG,kBACtBH,EAAE,2BAA2B,CAC1B,CAAA,CACP,CAAA,CAEO,CAAA,CACF,CAAA,CAAA,CAEhB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{J as e,Z as t,i as n,n as r,q as i,s as a,t as o}from"./createLucideIcon-BPpAIXR_.js";import{On as s,Sn as c,Sr as l,t as u}from"./config-DTQCUlfZ.js";import{t as d}from"./useMutation-BWZ0G4Td.js";import{
|
|
2
|
-
//# sourceMappingURL=passkey-
|
|
1
|
+
import{J as e,Z as t,i as n,n as r,q as i,s as a,t as o}from"./createLucideIcon-BPpAIXR_.js";import{On as s,Sn as c,Sr as l,t as u}from"./config-DTQCUlfZ.js";import{t as d}from"./useMutation-BWZ0G4Td.js";import{nt as f}from"./shell-header-bar-BPgCjFPv.js";import{t as p}from"./auth-field-BSlAWkbx.js";import{t as m}from"./fingerprint-pattern-DP62Vk8x.js";import{t as h}from"./shield-check-D_p8H0ni.js";import{c as g,o as _}from"./zod-C87S6Tec.js";import{F as v,N as y,R as b,S as x,j as S,u as C,z as w}from"./index-H6CJKjiN.js";import{t as T}from"./auth-page-header-ZLhtQ7Et.js";import{t as E}from"./alert-leqyvHOp.js";import{t as D}from"./auth-layout-CcJy6dVY.js";import{t as O}from"./promise-OpBtq8tG.js";import{n as k,t as A}from"./auth-footer-Cz5ivsDc.js";import{t as j}from"./passkey-error-ZqqPQUta.js";import{i as M}from"./passkey-J1qnYp9A.js";import{r as N,t as P}from"./standard-schema-e8gtSzGE.js";var F=o(`tag`,[[`path`,{d:`M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z`,key:`vktsd0`}],[`circle`,{cx:`7.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`kqv944`}]]),I=t(e()),L=i();function R(){let{t:e}=n(),t=s(),i=l(),o=C.useSearch(),{data:R}=f(u),[z,B]=(0,I.useState)(o.passkey_name?`registering`:`form`),[V,H]=(0,I.useState)(``),U=(0,I.useRef)(!1),W=R.auth.password.enabled&&R.auth.password.two_factor.enrollment_required&&R.auth.password.totp.enabled,{register:G,handleSubmit:K,formState:{errors:q}}=N({defaultValues:{name:``},resolver:P((0,I.useMemo)(()=>_({name:g().max(100,e(`validation.passkey.name.max`))}),[e]))}),J=d({...M,onSuccess:async e=>{e.second_factor_setup_completed&&e.user?(i.setQueryData(x.queryKey,{user:e.user}),await O(),v(o)?window.location.href=S(o):t.navigate({to:`/profile`})):(i.invalidateQueries({queryKey:x.queryKey}),t.navigate({to:`/profile`}))},onError:t=>{switch(B(`error`),j(t)){case`unsupported`:H(e(`setupPasskey.error.unsupported`));return;case`not_allowed`:H(e(`setupPasskey.error.notAllowed`));return;case`expired`:H(e(`setupPasskey.error.expired`));return;case`user_mismatch`:case`verification_failed`:H(e(`setupPasskey.error.failed`));return}},onSettled:()=>{i.invalidateQueries({queryKey:x.queryKey})}});return(0,I.useEffect)(()=>{o.passkey_name&&!U.current&&(U.current=!0,J.mutate({name:o.passkey_name}))},[J,o.passkey_name]),z===`registering`?(0,L.jsxs)(D,{children:[(0,L.jsx)(T,{subtitle:e(`setupPasskey.subtitle`),title:e(`setupPasskey.title`)}),(0,L.jsxs)(`div`,{className:`flex flex-col items-center gap-tinyrack-lg py-tinyrack-xl`,children:[(0,L.jsx)(m,{"aria-hidden":!0,className:`size-tinyrack-3xl animate-pulse text-tinyrack-primary-foreground`}),(0,L.jsx)(r,{align:`center`,as:`p`,color:`muted`,variant:`body`,children:e(`setupPasskey.waiting`)})]})]}):z===`error`?(0,L.jsxs)(D,{children:[(0,L.jsx)(T,{subtitle:e(`setupPasskey.subtitle`),title:e(`setupPasskey.title`)}),(0,L.jsx)(E,{icon:b,type:`error`,children:V}),(0,L.jsxs)(`div`,{className:`flex flex-col gap-tinyrack-sm`,children:[W&&(0,L.jsxs)(w,{className:`w-full gap-tinyrack-sm`,intent:`primary`,render:(0,L.jsx)(c,{search:y(o),to:`/setup/totp`}),uiSize:`lg`,children:[(0,L.jsx)(h,{"aria-hidden":!0,className:`size-tinyrack-xl`}),e(`setupPasskey.useTotp`)]}),(0,L.jsx)(a,{appearance:W?`outline`:`solid`,className:`w-full`,intent:W?`neutral`:`primary`,onClick:()=>{o.passkey_name?(B(`registering`),H(``),J.mutate({name:o.passkey_name})):B(`form`)},type:`button`,uiSize:`lg`,children:e(`setupPasskey.retry`)})]}),(0,L.jsx)(A,{children:(0,L.jsx)(k,{link:(0,L.jsx)(c,{search:y(o),to:`/login`,children:e(`setupPasskey.backToLogin`)})})})]}):(0,L.jsxs)(D,{children:[(0,L.jsx)(T,{subtitle:e(`setupPasskey.subtitle`),title:e(`setupPasskey.title`)}),(0,L.jsx)(E,{icon:h,type:`info`,children:e(`setupPasskey.required`)}),(0,L.jsx)(r,{align:`center`,as:`p`,color:`muted`,variant:`bodySm`,children:e(`setupPasskey.description`)}),(0,L.jsxs)(`form`,{className:`flex flex-col gap-tinyrack-lg`,onSubmit:K(e=>{B(`registering`),H(``),J.mutate({name:e.name||void 0})}),children:[(0,L.jsx)(p,{error:q.name,hint:e(`setupPasskey.name.hint`),icon:F,id:`passkey-name`,label:e(`setupPasskey.name.label`),placeholder:e(`setupPasskey.name.placeholder`),...G(`name`),type:`text`}),(0,L.jsx)(a,{className:`w-full`,intent:`primary`,loading:J.isPending,loadingLabel:e(`setupPasskey.registering`),type:`submit`,uiSize:`lg`,children:e(`setupPasskey.continue`)})]}),(0,L.jsx)(A,{children:(0,L.jsx)(k,{link:(0,L.jsx)(c,{search:y(o),to:`/login`,children:e(`setupPasskey.backToLogin`)})})})]})}export{R as component};
|
|
2
|
+
//# sourceMappingURL=passkey-V_tav53p.js.map
|