@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-BE9JvJOP.js","names":["standardSchemaResolver","useMutation","useQueryClient","useSuspenseQuery","Link","useRouter","TRButton","TRLinkButton","TRText","CircleAlertIcon","FingerprintIcon","ShieldCheckIcon","TagIcon","useEffect","useMemo","useRef","useState","useForm","useTranslation","z","AuthField","AuthFooter","AuthFooterLink","AuthPageHeader","Alert","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","classifyPasskeyError","tick","appConfigQueryOptions","registerPasskeyMutationOptions","getSessionQueryOptions","Route","SetupStep","SetupPasskey","t","router","queryClient","search","useSearch","data","appConfig","step","setStep","passkey_name","errorMessage","setErrorMessage","autoRegisterCalledRef","canUseTotpSetup","auth","password","enabled","two_factor","enrollment_required","totp","formSchema","object","name","string","max","FormValues","infer","register","handleSubmit","formState","errors","defaultValues","resolver","registerMutation","onSuccess","second_factor_setup_completed","user","setQueryData","queryKey","window","location","href","navigate","to","invalidateQueries","onError","error","reason","onSettled","current","mutate","onSubmit","values","undefined","isPending","component"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.25.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/tag.mjs","../../../frontend/src/routes/setup/passkey/index.tsx?tsr-split=component"],"sourcesContent":["/**\n * @license lucide-react v1.25.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n 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\",\n key: \"vktsd0\"\n }\n ],\n [\"circle\", { cx: \"7.5\", cy: \"7.5\", r: \".5\", fill: \"currentColor\", key: \"kqv944\" }]\n];\nconst Tag = createLucideIcon(\"tag\", __iconNode);\n\nexport { __iconNode, Tag as default };\n//# sourceMappingURL=tag.mjs.map\n","import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} 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 { TRText } from '@tinyrack/ui/components/text';\nimport {\n CircleAlertIcon,\n FingerprintIcon,\n ShieldCheckIcon,\n TagIcon,\n} from 'lucide-react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\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 { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.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 { classifyPasskeyError } from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { registerPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema.extend({\n passkey_name: z.string().optional(),\n});\n\nexport const Route = createFileRoute('/setup/passkey/')({\n component: SetupPasskey,\n errorComponent: RouteErrorFallback,\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n validateSearch: SearchSchema,\n});\n\ntype SetupStep = 'form' | 'registering' | 'error';\n\nfunction SetupPasskey() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const [step, setStep] = useState<SetupStep>(\n search.passkey_name ? 'registering' : 'form',\n );\n const [errorMessage, setErrorMessage] = useState<string>('');\n const autoRegisterCalledRef = useRef(false);\n const canUseTotpSetup =\n appConfig.auth.password.enabled &&\n appConfig.auth.password.two_factor.enrollment_required &&\n appConfig.auth.password.totp.enabled;\n\n const formSchema = useMemo(\n () =>\n z.object({\n name: z.string().max(100, t('validation.passkey.name.max')),\n }),\n [t],\n );\n\n type FormValues = z.infer<typeof formSchema>;\n\n const {\n register,\n handleSubmit,\n formState: { errors },\n } = useForm<FormValues>({\n defaultValues: { name: '' },\n resolver: standardSchemaResolver(formSchema),\n });\n\n const registerMutation = useMutation({\n ...registerPasskeyMutationOptions,\n onSuccess: async (data) => {\n if (data.second_factor_setup_completed && 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 } else {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n router.navigate({ to: '/profile' });\n }\n },\n onError: (error) => {\n setStep('error');\n const reason = classifyPasskeyError(error);\n\n switch (reason) {\n case 'unsupported':\n setErrorMessage(t('setupPasskey.error.unsupported'));\n return;\n case 'not_allowed':\n setErrorMessage(t('setupPasskey.error.notAllowed'));\n return;\n case 'expired':\n setErrorMessage(t('setupPasskey.error.expired'));\n return;\n case 'user_mismatch':\n case 'verification_failed':\n setErrorMessage(t('setupPasskey.error.failed'));\n return;\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n useEffect(() => {\n if (search.passkey_name && !autoRegisterCalledRef.current) {\n autoRegisterCalledRef.current = true;\n registerMutation.mutate({ name: search.passkey_name });\n }\n }, [registerMutation, search.passkey_name]);\n\n const onSubmit = (values: FormValues) => {\n setStep('registering');\n setErrorMessage('');\n registerMutation.mutate({ name: values.name || undefined });\n };\n\n // Registering state - waiting for WebAuthn\n if (step === 'registering') {\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n <div className=\"flex flex-col items-center gap-tinyrack-lg py-tinyrack-xl\">\n <FingerprintIcon\n aria-hidden\n className=\"size-tinyrack-3xl animate-pulse text-tinyrack-primary\"\n />\n <TRText align=\"center\" as=\"p\" color=\"muted\" variant=\"body\">\n {t('setupPasskey.waiting')}\n </TRText>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (step === 'error') {\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n <Alert icon={CircleAlertIcon} type=\"error\">\n {errorMessage}\n </Alert>\n\n <div className=\"flex flex-col gap-tinyrack-sm\">\n {canUseTotpSetup && (\n <TRLinkButton\n className=\"w-full gap-tinyrack-sm\"\n intent=\"primary\"\n render={\n <Link search={extractOAuthParams(search)} to=\"/setup/totp\" />\n }\n uiSize=\"lg\"\n >\n <ShieldCheckIcon aria-hidden className=\"size-tinyrack-xl\" />\n {t('setupPasskey.useTotp')}\n </TRLinkButton>\n )}\n <TRButton\n appearance={canUseTotpSetup ? 'outline' : 'solid'}\n className=\"w-full\"\n intent={canUseTotpSetup ? 'neutral' : 'primary'}\n onClick={() => {\n if (search.passkey_name) {\n setStep('registering');\n setErrorMessage('');\n registerMutation.mutate({ name: search.passkey_name });\n } else {\n setStep('form');\n }\n }}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupPasskey.retry')}\n </TRButton>\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n\n <Alert icon={ShieldCheckIcon} type=\"info\">\n {t('setupPasskey.required')}\n </Alert>\n\n <TRText align=\"center\" as=\"p\" color=\"muted\" variant=\"bodySm\">\n {t('setupPasskey.description')}\n </TRText>\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n error={errors.name}\n hint={t('setupPasskey.name.hint')}\n icon={TagIcon}\n id=\"passkey-name\"\n label={t('setupPasskey.name.label')}\n placeholder={t('setupPasskey.name.placeholder')}\n {...register('name')}\n type=\"text\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={registerMutation.isPending}\n loadingLabel={t('setupPasskey.registering')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('setupPasskey.continue')}\n </TRButton>\n </form>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"passkey-V_tav53p.js","names":["standardSchemaResolver","useMutation","useQueryClient","useSuspenseQuery","Link","useRouter","TRButton","TRLinkButton","TRText","CircleAlertIcon","FingerprintIcon","ShieldCheckIcon","TagIcon","useEffect","useMemo","useRef","useState","useForm","useTranslation","z","AuthField","AuthFooter","AuthFooterLink","AuthPageHeader","Alert","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","classifyPasskeyError","tick","appConfigQueryOptions","registerPasskeyMutationOptions","getSessionQueryOptions","Route","SetupStep","SetupPasskey","t","router","queryClient","search","useSearch","data","appConfig","step","setStep","passkey_name","errorMessage","setErrorMessage","autoRegisterCalledRef","canUseTotpSetup","auth","password","enabled","two_factor","enrollment_required","totp","formSchema","object","name","string","max","FormValues","infer","register","handleSubmit","formState","errors","defaultValues","resolver","registerMutation","onSuccess","second_factor_setup_completed","user","setQueryData","queryKey","window","location","href","navigate","to","invalidateQueries","onError","error","reason","onSettled","current","mutate","onSubmit","values","undefined","isPending","component"],"sources":["../../../../node_modules/.pnpm/lucide-react@1.25.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/tag.mjs","../../../frontend/src/routes/setup/passkey/index.tsx?tsr-split=component"],"sourcesContent":["/**\n * @license lucide-react v1.25.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n 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\",\n key: \"vktsd0\"\n }\n ],\n [\"circle\", { cx: \"7.5\", cy: \"7.5\", r: \".5\", fill: \"currentColor\", key: \"kqv944\" }]\n];\nconst Tag = createLucideIcon(\"tag\", __iconNode);\n\nexport { __iconNode, Tag as default };\n//# sourceMappingURL=tag.mjs.map\n","import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} 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 { TRText } from '@tinyrack/ui/components/text';\nimport {\n CircleAlertIcon,\n FingerprintIcon,\n ShieldCheckIcon,\n TagIcon,\n} from 'lucide-react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\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 { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.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 { classifyPasskeyError } from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { registerPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema.extend({\n passkey_name: z.string().optional(),\n});\n\nexport const Route = createFileRoute('/setup/passkey/')({\n component: SetupPasskey,\n errorComponent: RouteErrorFallback,\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n validateSearch: SearchSchema,\n});\n\ntype SetupStep = 'form' | 'registering' | 'error';\n\nfunction SetupPasskey() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const [step, setStep] = useState<SetupStep>(\n search.passkey_name ? 'registering' : 'form',\n );\n const [errorMessage, setErrorMessage] = useState<string>('');\n const autoRegisterCalledRef = useRef(false);\n const canUseTotpSetup =\n appConfig.auth.password.enabled &&\n appConfig.auth.password.two_factor.enrollment_required &&\n appConfig.auth.password.totp.enabled;\n\n const formSchema = useMemo(\n () =>\n z.object({\n name: z.string().max(100, t('validation.passkey.name.max')),\n }),\n [t],\n );\n\n type FormValues = z.infer<typeof formSchema>;\n\n const {\n register,\n handleSubmit,\n formState: { errors },\n } = useForm<FormValues>({\n defaultValues: { name: '' },\n resolver: standardSchemaResolver(formSchema),\n });\n\n const registerMutation = useMutation({\n ...registerPasskeyMutationOptions,\n onSuccess: async (data) => {\n if (data.second_factor_setup_completed && 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 } else {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n router.navigate({ to: '/profile' });\n }\n },\n onError: (error) => {\n setStep('error');\n const reason = classifyPasskeyError(error);\n\n switch (reason) {\n case 'unsupported':\n setErrorMessage(t('setupPasskey.error.unsupported'));\n return;\n case 'not_allowed':\n setErrorMessage(t('setupPasskey.error.notAllowed'));\n return;\n case 'expired':\n setErrorMessage(t('setupPasskey.error.expired'));\n return;\n case 'user_mismatch':\n case 'verification_failed':\n setErrorMessage(t('setupPasskey.error.failed'));\n return;\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n useEffect(() => {\n if (search.passkey_name && !autoRegisterCalledRef.current) {\n autoRegisterCalledRef.current = true;\n registerMutation.mutate({ name: search.passkey_name });\n }\n }, [registerMutation, search.passkey_name]);\n\n const onSubmit = (values: FormValues) => {\n setStep('registering');\n setErrorMessage('');\n registerMutation.mutate({ name: values.name || undefined });\n };\n\n // Registering state - waiting for WebAuthn\n if (step === 'registering') {\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n <div className=\"flex flex-col items-center gap-tinyrack-lg py-tinyrack-xl\">\n <FingerprintIcon\n aria-hidden\n className=\"size-tinyrack-3xl animate-pulse text-tinyrack-primary-foreground\"\n />\n <TRText align=\"center\" as=\"p\" color=\"muted\" variant=\"body\">\n {t('setupPasskey.waiting')}\n </TRText>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (step === 'error') {\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n <Alert icon={CircleAlertIcon} type=\"error\">\n {errorMessage}\n </Alert>\n\n <div className=\"flex flex-col gap-tinyrack-sm\">\n {canUseTotpSetup && (\n <TRLinkButton\n className=\"w-full gap-tinyrack-sm\"\n intent=\"primary\"\n render={\n <Link search={extractOAuthParams(search)} to=\"/setup/totp\" />\n }\n uiSize=\"lg\"\n >\n <ShieldCheckIcon aria-hidden className=\"size-tinyrack-xl\" />\n {t('setupPasskey.useTotp')}\n </TRLinkButton>\n )}\n <TRButton\n appearance={canUseTotpSetup ? 'outline' : 'solid'}\n className=\"w-full\"\n intent={canUseTotpSetup ? 'neutral' : 'primary'}\n onClick={() => {\n if (search.passkey_name) {\n setStep('registering');\n setErrorMessage('');\n registerMutation.mutate({ name: search.passkey_name });\n } else {\n setStep('form');\n }\n }}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupPasskey.retry')}\n </TRButton>\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n\n <Alert icon={ShieldCheckIcon} type=\"info\">\n {t('setupPasskey.required')}\n </Alert>\n\n <TRText align=\"center\" as=\"p\" color=\"muted\" variant=\"bodySm\">\n {t('setupPasskey.description')}\n </TRText>\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n error={errors.name}\n hint={t('setupPasskey.name.hint')}\n icon={TagIcon}\n id=\"passkey-name\"\n label={t('setupPasskey.name.label')}\n placeholder={t('setupPasskey.name.placeholder')}\n {...register('name')}\n type=\"text\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={registerMutation.isPending}\n loadingLabel={t('setupPasskey.registering')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('setupPasskey.continue')}\n </TRButton>\n </form>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-
|
|
1
|
+
{"version":3,"file":"passkey-tzTSqiOS.js","names":["RouteErrorFallback","SetupStep","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/setup/passkey/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLinkButton } from '@tinyrack/ui/components/link-button';\nimport { TRText } from '@tinyrack/ui/components/text';\nimport {\n CircleAlertIcon,\n FingerprintIcon,\n ShieldCheckIcon,\n TagIcon,\n} from 'lucide-react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\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 { RouteErrorFallback } from '#frontend/components/ui/route-error-fallback.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 { classifyPasskeyError } from '#frontend/libs/passkey-error.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { registerPasskeyMutationOptions } from '#frontend/queries/passkey.ts';\nimport { getSessionQueryOptions } from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema.extend({\n passkey_name: z.string().optional(),\n});\n\nexport const Route = createFileRoute('/setup/passkey/')({\n component: SetupPasskey,\n errorComponent: RouteErrorFallback,\n loader: async ({ context }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n },\n validateSearch: SearchSchema,\n});\n\ntype SetupStep = 'form' | 'registering' | 'error';\n\nfunction SetupPasskey() {\n const { t } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const { data: appConfig } = useSuspenseQuery(appConfigQueryOptions);\n\n const [step, setStep] = useState<SetupStep>(\n search.passkey_name ? 'registering' : 'form',\n );\n const [errorMessage, setErrorMessage] = useState<string>('');\n const autoRegisterCalledRef = useRef(false);\n const canUseTotpSetup =\n appConfig.auth.password.enabled &&\n appConfig.auth.password.two_factor.enrollment_required &&\n appConfig.auth.password.totp.enabled;\n\n const formSchema = useMemo(\n () =>\n z.object({\n name: z.string().max(100, t('validation.passkey.name.max')),\n }),\n [t],\n );\n\n type FormValues = z.infer<typeof formSchema>;\n\n const {\n register,\n handleSubmit,\n formState: { errors },\n } = useForm<FormValues>({\n defaultValues: { name: '' },\n resolver: standardSchemaResolver(formSchema),\n });\n\n const registerMutation = useMutation({\n ...registerPasskeyMutationOptions,\n onSuccess: async (data) => {\n if (data.second_factor_setup_completed && 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 } else {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n router.navigate({ to: '/profile' });\n }\n },\n onError: (error) => {\n setStep('error');\n const reason = classifyPasskeyError(error);\n\n switch (reason) {\n case 'unsupported':\n setErrorMessage(t('setupPasskey.error.unsupported'));\n return;\n case 'not_allowed':\n setErrorMessage(t('setupPasskey.error.notAllowed'));\n return;\n case 'expired':\n setErrorMessage(t('setupPasskey.error.expired'));\n return;\n case 'user_mismatch':\n case 'verification_failed':\n setErrorMessage(t('setupPasskey.error.failed'));\n return;\n }\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n useEffect(() => {\n if (search.passkey_name && !autoRegisterCalledRef.current) {\n autoRegisterCalledRef.current = true;\n registerMutation.mutate({ name: search.passkey_name });\n }\n }, [registerMutation, search.passkey_name]);\n\n const onSubmit = (values: FormValues) => {\n setStep('registering');\n setErrorMessage('');\n registerMutation.mutate({ name: values.name || undefined });\n };\n\n // Registering state - waiting for WebAuthn\n if (step === 'registering') {\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n <div className=\"flex flex-col items-center gap-tinyrack-lg py-tinyrack-xl\">\n <FingerprintIcon\n aria-hidden\n className=\"size-tinyrack-3xl animate-pulse text-tinyrack-primary-foreground\"\n />\n <TRText align=\"center\" as=\"p\" color=\"muted\" variant=\"body\">\n {t('setupPasskey.waiting')}\n </TRText>\n </div>\n </AuthLayout>\n );\n }\n\n // Error state\n if (step === 'error') {\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n <Alert icon={CircleAlertIcon} type=\"error\">\n {errorMessage}\n </Alert>\n\n <div className=\"flex flex-col gap-tinyrack-sm\">\n {canUseTotpSetup && (\n <TRLinkButton\n className=\"w-full gap-tinyrack-sm\"\n intent=\"primary\"\n render={\n <Link search={extractOAuthParams(search)} to=\"/setup/totp\" />\n }\n uiSize=\"lg\"\n >\n <ShieldCheckIcon aria-hidden className=\"size-tinyrack-xl\" />\n {t('setupPasskey.useTotp')}\n </TRLinkButton>\n )}\n <TRButton\n appearance={canUseTotpSetup ? 'outline' : 'solid'}\n className=\"w-full\"\n intent={canUseTotpSetup ? 'neutral' : 'primary'}\n onClick={() => {\n if (search.passkey_name) {\n setStep('registering');\n setErrorMessage('');\n registerMutation.mutate({ name: search.passkey_name });\n } else {\n setStep('form');\n }\n }}\n type=\"button\"\n uiSize=\"lg\"\n >\n {t('setupPasskey.retry')}\n </TRButton>\n </div>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n }\n\n // Form state\n return (\n <AuthLayout>\n <AuthPageHeader\n subtitle={t('setupPasskey.subtitle')}\n title={t('setupPasskey.title')}\n />\n\n <Alert icon={ShieldCheckIcon} type=\"info\">\n {t('setupPasskey.required')}\n </Alert>\n\n <TRText align=\"center\" as=\"p\" color=\"muted\" variant=\"bodySm\">\n {t('setupPasskey.description')}\n </TRText>\n\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n error={errors.name}\n hint={t('setupPasskey.name.hint')}\n icon={TagIcon}\n id=\"passkey-name\"\n label={t('setupPasskey.name.label')}\n placeholder={t('setupPasskey.name.placeholder')}\n {...register('name')}\n type=\"text\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={registerMutation.isPending}\n loadingLabel={t('setupPasskey.registering')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('setupPasskey.continue')}\n </TRButton>\n </form>\n\n <AuthFooter>\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('setupPasskey.backToLogin')}\n </Link>\n }\n />\n </AuthFooter>\n </AuthLayout>\n );\n}\n"],"mappings":"wCAsDkD,IAAAE,EA3BzCF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{J as e,Z as t,i as n,q as r,s as i}from"./createLucideIcon-BPpAIXR_.js";import{On as a,Sn as o,Sr as s,i as c,r as l,t as u}from"./config-DTQCUlfZ.js";import{t as d}from"./mutationOptions-DyYFUbyn.js";import{t as f}from"./useMutation-BWZ0G4Td.js";import{
|
|
2
|
-
//# sourceMappingURL=password-
|
|
1
|
+
import{J as e,Z as t,i as n,q as r,s as i}from"./createLucideIcon-BPpAIXR_.js";import{On as a,Sn as o,Sr as s,i as c,r as l,t as u}from"./config-DTQCUlfZ.js";import{t as d}from"./mutationOptions-DyYFUbyn.js";import{t as f}from"./useMutation-BWZ0G4Td.js";import{nt as p}from"./shell-header-bar-BPgCjFPv.js";import{t as m}from"./auth-field-BSlAWkbx.js";import{t as h}from"./authorization-context-banner-DfkjwE93.js";import{t as g}from"./lock-7NRyvXBS.js";import{t as _}from"./mail-D0B_1Dzd.js";import{c as v,i as y,o as b}from"./zod-C87S6Tec.js";import{F as x,N as S,S as C,j as w,s as T}from"./index-H6CJKjiN.js";import{t as E}from"./auth-page-header-ZLhtQ7Et.js";import{t as D}from"./auth-layout-CcJy6dVY.js";import{t as O}from"./promise-OpBtq8tG.js";import{t as k}from"./link-C-WdTj3F.js";import{n as A,t as j}from"./auth-footer-Cz5ivsDc.js";import{o as M}from"./passkey-J1qnYp9A.js";import{r as N,t as P}from"./standard-schema-e8gtSzGE.js";var F=t(e()),I=d({mutationFn:async e=>c(await l.api.auth.login.$post({json:e}))}),L=r();function R(){let{t:e,i18n:t}=n(),r=a(),c=s(),l=T.useSearch(),d=l.account_selection_state&&l.prompt?.split(` `).includes(`login`)?{...l,account_selected:1}:l,R=l.lang??t.language,{data:z}=p(u),B=z.registration.signup_notice?.[R]??z.registration.signup_notice?.[z.i18n.fallback_language],V=z.auth.password.enabled,H=z.auth.passkey.enabled,U=z.identity_providers.length+ +!!V+ +!!H>1,W=(0,F.useMemo)(()=>b({email:v().min(1,e(`validation.email.required`)).pipe(y(e(`validation.email.invalid`))),password:v().min(1,e(`validation.password.required`))}),[e]),G=f({...I,onSuccess:async(e,t)=>{let n=e.user;if(n.email_verification_required&&!n.email_verified){r.navigate({to:`/verify/email`,search:{email:t.email,...S(l)}});return}c.setQueryData(C.queryKey,{user:n}),await O();let i=[];if(z.auth.password.totp.enabled&&n.totp_registered&&i.push(`totp`),z.auth.passkey.enabled&&n.passkey_count>0&&i.push(`passkey`),n.second_factor_required&&i.length===0){if(z.available_2fa_setup_methods.length>1)return r.navigate({to:`/setup/2fa`,search:S(l)});if(z.available_2fa_setup_methods.length===1)return z.available_2fa_setup_methods[0]===`totp`?r.navigate({to:`/setup/totp`,search:S(l)}):r.navigate({to:`/setup/passkey`,search:{...S(l),passkey_name:`default`}})}if(i.length>1)return r.navigate({to:`/verify/2fa`,search:S(l)});if(i.length===1)return i[0]===`totp`?r.navigate({to:`/verify/totp`,search:S(l)}):r.navigate({to:`/verify/passkey`,search:S(l)});if(x(l))window.location.href=w(d);else return r.navigate({to:`/profile`})},onError:t=>{Y(`email`,{type:`manual`,message:e(`login.error.failed`)})},onSettled:()=>{c.invalidateQueries({queryKey:C.queryKey})}}),K=(0,F.useCallback)(async e=>{e.user&&(c.setQueryData(C.queryKey,e),await O(),x(l)?window.location.href=w(d):r.navigate({to:`/profile`})),c.invalidateQueries({queryKey:C.queryKey})},[c,r,l,d]),q=(0,F.useRef)(null);(0,F.useEffect)(()=>{if(!(!H||!V))return q.current=new AbortController,M(K,q.current.signal),()=>{q.current?.abort()}},[H,V,K]);let{register:J,setError:Y,handleSubmit:X,formState:{errors:Z}}=N({defaultValues:{email:``,password:``},resolver:P(W)});return(0,L.jsxs)(D,{children:[(0,L.jsx)(E,{subtitle:e(`login.selectMethod.subtitle`)||void 0,title:e(`login.title`)}),(0,L.jsx)(h,{search:l}),V&&(0,L.jsxs)(`form`,{className:`flex flex-col gap-tinyrack-lg`,onSubmit:X(async e=>{G.mutate(e)}),children:[(0,L.jsx)(m,{autoComplete:`username webauthn`,error:Z.email,icon:_,label:e(`login.email.label`),placeholder:e(`login.email.placeholder`),...J(`email`),type:`email`}),(0,L.jsx)(m,{autoComplete:`current-password`,error:Z.password,icon:g,label:e(`login.password.label`),labelAction:z.email.enabled?(0,L.jsx)(k,{className:`text-tinyrack-xs`,render:(0,L.jsx)(o,{to:`/password/forgot`}),children:e(`login.link.forgotPassword`)}):void 0,placeholder:e(`login.password.placeholder`),...J(`password`),type:`password`}),(0,L.jsx)(i,{className:`w-full`,intent:`primary`,loading:G.isPending,loadingLabel:e(`login.submitting`),type:`submit`,uiSize:`lg`,children:e(`login.submit`)})]}),B&&(0,L.jsx)(`div`,{className:`prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!`,dangerouslySetInnerHTML:{__html:B}}),(z.registration.public_registration||U)&&(0,L.jsxs)(j,{children:[z.registration.public_registration&&(0,L.jsx)(A,{link:(0,L.jsx)(o,{search:S(l),to:`/register`,children:e(`login.link.register`)}),text:e(`login.footer.noAccount`)}),U&&(0,L.jsx)(A,{link:(0,L.jsx)(o,{search:S(l),to:`/login`,children:e(`login.password.backToMethods`)})})]})]})}export{R as component};
|
|
2
|
+
//# sourceMappingURL=password-6QOqTV_s.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-RkUttbhD.js","names":["standardSchemaResolver","useMutation","useQueryClient","useSuspenseQuery","Link","useRouter","TRButton","TRLink","LockIcon","MailIcon","useCallback","useEffect","useMemo","useRef","useForm","useTranslation","z","AuthField","AuthFooter","AuthFooterLink","AuthPageHeader","AuthorizationContextBanner","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","appConfigQueryOptions","loginMutationOptions","startConditionalPasskeyAuth","getSessionQueryOptions","Route","LoginPassword","t","i18n","router","queryClient","search","useSearch","authorizeSearch","OAuthSearch","account_selection_state","prompt","split","includes","account_selected","lang","language","data","configData","implicitNotice","registration","signup_notice","fallback_language","isPasswordAuthEnabled","auth","password","enabled","isPasskeyEnabled","passkey","hasMultipleLoginMethods","identity_providers","length","loginSchema","object","email","string","min","pipe","loginMutation","onSuccess","params","user","email_verification_required","email_verified","navigate","to","setQueryData","queryKey","registered_2fa_methods","SecondFactorMethod","totp","totp_registered","push","passkey_count","second_factor_required","available_2fa_setup_methods","method","passkey_name","window","location","href","onError","_error","setError","type","message","onSettled","invalidateQueries","handlePasskeySuccess","AuthResponse","abortControllerRef","AbortController","current","signal","abort","register","handleSubmit","formState","errors","defaultValues","resolver","onSubmit","values","infer","mutate","undefined","isPending","__html","public_registration","component"],"sources":["../../../frontend/src/queries/login.ts","../../../frontend/src/routes/login/password/index.tsx?tsr-split=component"],"sourcesContent":["import { mutationOptions } from '@tanstack/react-query';\nimport type { InferRequestType, InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\n\nexport type LoginParams = InferRequestType<\n (typeof client.api.auth.login)['$post']\n>['json'];\n\nexport type LoginResponse = InferResponseType<\n (typeof client.api.auth.login)['$post'],\n 200\n>;\n\nexport const loginMutationOptions = mutationOptions({\n mutationFn: async (values: LoginParams) => {\n const res = await client.api.auth.login.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n","import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLink } from '@tinyrack/ui/components/link';\nimport { LockIcon, MailIcon } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.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 type OAuthSearch,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { loginMutationOptions } from '#frontend/queries/login.ts';\nimport { startConditionalPasskeyAuth } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/login/password/')({\n component: LoginPassword,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction LoginPassword() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const authorizeSearch: OAuthSearch =\n search.account_selection_state &&\n search.prompt?.split(' ').includes('login')\n ? { ...search, account_selected: 1 }\n : search;\n const lang = search.lang ?? i18n.language;\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const hasMultipleLoginMethods =\n configData.identity_providers.length +\n (isPasswordAuthEnabled ? 1 : 0) +\n (isPasskeyEnabled ? 1 : 0) >\n 1;\n\n const loginSchema = useMemo(\n () =>\n z.object({\n email: z\n .string()\n .min(1, t('validation.email.required'))\n .pipe(z.email(t('validation.email.invalid'))),\n password: z.string().min(1, t('validation.password.required')),\n }),\n [t],\n );\n\n const loginMutation = useMutation({\n ...loginMutationOptions,\n onSuccess: async (data, params) => {\n const user = data.user;\n\n if (user.email_verification_required && !user.email_verified) {\n router.navigate({\n to: '/verify/email',\n search: {\n email: params.email,\n ...extractOAuthParams(search),\n },\n });\n return;\n }\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n const registered_2fa_methods: SecondFactorMethod[] = [];\n if (configData.auth.password.totp.enabled && user.totp_registered) {\n registered_2fa_methods.push('totp');\n }\n if (configData.auth.passkey.enabled && user.passkey_count > 0) {\n registered_2fa_methods.push('passkey');\n }\n\n if (user.second_factor_required && registered_2fa_methods.length === 0) {\n if (configData.available_2fa_setup_methods.length > 1) {\n return router.navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n } else if (configData.available_2fa_setup_methods.length === 1) {\n const method = configData.available_2fa_setup_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/setup/passkey',\n search: {\n ...extractOAuthParams(search),\n passkey_name: 'default',\n },\n });\n }\n }\n }\n\n if (registered_2fa_methods.length > 1) {\n return router.navigate({\n to: '/verify/2fa',\n search: extractOAuthParams(search),\n });\n } else if (registered_2fa_methods.length === 1) {\n const method = registered_2fa_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/verify/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/verify/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n if (isOAuthFlow(search)) {\n window.location.href =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n return router.navigate({ to: '/profile' });\n }\n }\n },\n onError: (_error) => {\n setError('email', {\n type: 'manual',\n message: t('login.error.failed'),\n });\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const handlePasskeySuccess = useCallback(\n 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 =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n [queryClient, router, search, authorizeSearch],\n );\n\n // Conditional UI: Start passkey autofill on page load\n const abortControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n if (!isPasskeyEnabled || !isPasswordAuthEnabled) {\n return;\n }\n\n abortControllerRef.current = new AbortController();\n startConditionalPasskeyAuth(\n handlePasskeySuccess,\n abortControllerRef.current.signal,\n );\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [isPasskeyEnabled, isPasswordAuthEnabled, handlePasskeySuccess]);\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm({\n defaultValues: {\n email: '',\n password: '',\n },\n resolver: standardSchemaResolver(loginSchema),\n });\n\n const onSubmit = async (values: z.infer<typeof loginSchema>) => {\n loginMutation.mutate(values);\n };\n\n return (\n <AuthLayout>\n {/* Branding lives in the layout's brand panel — see /login. */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {isPasswordAuthEnabled && (\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n autoComplete=\"username webauthn\"\n error={errors.email}\n icon={MailIcon}\n label={t('login.email.label')}\n placeholder={t('login.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <AuthField\n autoComplete=\"current-password\"\n error={errors.password}\n icon={LockIcon}\n label={t('login.password.label')}\n /*\n * On the label row rather than under the field: it belongs to the\n * password, and putting it between the field and the submit button\n * separated the two things the user is actually trying to do.\n */\n labelAction={\n configData.email.enabled ? (\n <TRLink\n className=\"text-tinyrack-xs\"\n render={<Link to=\"/password/forgot\" />}\n >\n {t('login.link.forgotPassword')}\n </TRLink>\n ) : undefined\n }\n placeholder={t('login.password.placeholder')}\n {...register('password')}\n type=\"password\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={loginMutation.isPending}\n loadingLabel={t('login.submitting')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('login.submit')}\n </TRButton>\n </form>\n )}\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {(configData.registration.public_registration ||\n hasMultipleLoginMethods) && (\n <AuthFooter>\n {configData.registration.public_registration && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/register\">\n {t('login.link.register')}\n </Link>\n }\n text={t('login.footer.noAccount')}\n />\n )}\n {hasMultipleLoginMethods && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('login.password.backToMethods')}\n </Link>\n }\n />\n )}\n </AuthFooter>\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"27BAaA,EAAA,EAAA,CAAA,WAAA,KAAA,6CAME,CAAA,QC0CF,SAASgC,GAAgB,CACvB,GAAM,CAAEC,IAAGC,QAASnB,EAAe,EAC7BoB,EAAS9B,EAAU,EACnB+B,EAAclC,EAAe,EAC7BmC,EAASN,EAAMO,UAAU,EACzBC,EACJF,EAAOI,yBACPJ,EAAOK,QAAQC,MAAM,GAAG,CAAC,CAACC,SAAS,OAAO,EACtC,CAAE,GAAGP,EAAQQ,iBAAkB,CAAE,EACjCR,EACAS,EAAOT,EAAOS,MAAQZ,EAAKa,SAE3B,CAAEC,KAAMC,GAAe9C,EAAiBwB,CAAqB,EAE7DuB,EACJD,EAAWE,aAAaC,gBAAgBN,IACxCG,EAAWE,aAAaC,gBAAgBH,EAAWf,KAAKmB,mBACpDC,EAAwBL,EAAWM,KAAKC,SAASC,QACjDC,EAAmBT,EAAWM,KAAKI,QAAQF,QAC3CG,EACJX,EAAWY,mBAAmBC,QAC3BR,MACAI,KACH,EAEIK,GAAAA,EAAAA,EAAAA,QAAAA,KAEF/C,EAAS,CACPiD,MAAOjD,EACG,CAAC,CACRmD,IAAI,EAAGlC,EAAE,2BAA2B,CAAC,CAAC,CACtCmC,KAAKpD,EAAQiB,EAAE,0BAA0B,CAAC,CAAC,EAC9CuB,SAAUxC,EAAS,CAAC,CAACmD,IAAI,EAAGlC,EAAE,8BAA8B,CAAC,CAC/D,CAAC,EACH,CAACA,CAAC,CACJ,EAEMoC,EAAgBpE,EAAY,CAChC,GAAG2B,EACH0C,UAAW,MAAOtB,EAAMuB,IAAW,CACjC,IAAMC,EAAOxB,EAAKwB,KAElB,GAAIA,EAAKC,6BAA+B,CAACD,EAAKE,eAAgB,CAC5DvC,EAAOwC,SAAS,CACdC,GAAI,gBACJvC,OAAQ,CACN4B,MAAOM,EAAON,MACd,GAAGzC,EAAmBa,CAAM,CAC9B,CACF,CAAC,EACD,MACF,CAEAD,EAAYyC,aAAa/C,EAAuBgD,SAAU,CAClDN,MACR,CAAC,EACD,MAAM9C,EAAK,EAEX,IAAMqD,EAA+C,CAAA,EAQrD,GAPI9B,EAAWM,KAAKC,SAASyB,KAAKxB,SAAWe,EAAKU,iBAChDH,EAAuBI,KAAK,MAAM,EAEhClC,EAAWM,KAAKI,QAAQF,SAAWe,EAAKY,cAAgB,GAC1DL,EAAuBI,KAAK,SAAS,EAGnCX,EAAKa,wBAA0BN,EAAuBjB,SAAW,EACnE,IAAIb,EAAWqC,4BAA4BxB,OAAS,EAClD,OAAO3B,EAAOwC,SAAS,CACrBC,GAAI,aACJvC,OAAQb,EAAmBa,CAAM,CACnC,CAAC,EACI,GAAIY,EAAWqC,4BAA4BxB,SAAW,EAQzD,OAPab,EAAWqC,4BAA4B,KACvC,OACNnD,EAAOwC,SAAS,CACrBC,GAAI,cACJvC,OAAQb,EAAmBa,CAAM,CACnC,CAAC,EAEMF,EAAOwC,SAAS,CACrBC,GAAI,iBACJvC,OAAQ,CACN,GAAGb,EAAmBa,CAAM,EAC5BmD,aAAc,SAChB,CACF,CAAC,CAEL,CAGF,GAAIT,EAAuBjB,OAAS,EAClC,OAAO3B,EAAOwC,SAAS,CACrBC,GAAI,cACJvC,OAAQb,EAAmBa,CAAM,CACnC,CAAC,EACI,GAAI0C,EAAuBjB,SAAW,EAQzC,OAPaiB,EAAuB,KACvB,OACN5C,EAAOwC,SAAS,CACrBC,GAAI,eACJvC,OAAQb,EAAmBa,CAAM,CACnC,CAAC,EAEMF,EAAOwC,SAAS,CACrBC,GAAI,kBACJvC,OAAQb,EAAmBa,CAAM,CACnC,CAAC,EAGH,GAAIZ,EAAYY,CAAM,EACpBoD,OAAOC,SAASC,KACdpE,EAA+BgB,CAAe,OAEhD,OAAOJ,EAAOwC,SAAS,CAAEC,GAAI,UAAW,CAAC,CAG/C,EACAgB,QAAUC,GAAW,CACnBC,EAAS,QAAS,CAChBC,KAAM,SACNC,QAAS/D,EAAE,oBAAoB,CACjC,CAAC,CACH,EACAgE,cAAiB,CACf7D,EAAY8D,kBAAkB,CAC5BpB,SAAUhD,EAAuBgD,QACnC,CAAC,CACH,CACF,CAAC,EAEKqB,GAAAA,EAAAA,EAAAA,YAAAA,CACJ,KAAOnD,IAAuB,CACxBA,EAAKwB,OACPpC,EAAYyC,aAAa/C,EAAuBgD,SAAU9B,CAAI,EAC9D,MAAMtB,EAAK,EAEPD,EAAYY,CAAM,EACpBoD,OAAOC,SAASC,KACdpE,EAA+BgB,CAAe,EAEhDJ,EAAOwC,SAAS,CAAEC,GAAI,UAAW,CAAC,GAGtCxC,EAAY8D,kBAAkB,CAC5BpB,SAAUhD,EAAuBgD,QACnC,CAAC,CACH,EACA,CAAC1C,EAAaD,EAAQE,EAAQE,CAAe,CAC/C,EAGM8D,GAAAA,EAAAA,EAAAA,OAAAA,CAAoD,IAAI,GAE9D1F,EAAAA,EAAAA,UAAAA,KAAgB,CACV,MAAC+C,GAAoB,CAACJ,GAU1B,MANA+C,GAAmBE,QAAU,IAAID,gBACjCzE,EACEsE,EACAE,EAAmBE,QAAQC,MAC7B,MAEa,CACXH,EAAmBE,SAASE,MAAM,CACpC,CACF,EAAG,CAAC/C,EAAkBJ,EAAuB6C,CAAoB,CAAC,EAElE,GAAM,CACJO,WACAZ,WACAa,eACAC,UAAW,CAAEC,WACX/F,EAAQ,CACVgG,cAAe,CACb7C,MAAO,GACPT,SAAU,EACZ,EACAuD,SAAU/G,EAAuB+D,CAAW,CAC9C,CAAC,EAMD,OACE,EAAA,EAAA,KAAA,CAAC,EAAD,CAAA,SAAA,EAEE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,SAAU9B,EAAE,6BAA6B,GAAKmF,IAAAA,GAC9C,MAAOnF,EAAE,aAAa,CAAE,CAAA,GAG1B,EAAA,EAAA,IAAA,CAAC,EAAD,CAAoCI,QAAO,CAAA,EAE1CiB,IACC,EAAA,EAAA,KAAA,CAAC,OAAD,CACE,UAAU,gCACV,SAAUqD,EAAaK,KAjBPC,IAAwC,CAC9D5C,EAAc8C,OAAOF,CAAM,CAC7B,CAeuC,WAFjC,EAIE,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,aAAa,oBACb,MAAOJ,EAAO5C,MACd,KAAMxD,EACN,MAAOwB,EAAE,mBAAmB,EAC5B,YAAaA,EAAE,yBAAyB,EACxC,GAAIyE,EAAS,OAAO,EACpB,KAAK,OAAO,CAAA,GAGd,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,aAAa,mBACb,MAAOG,EAAOrD,SACd,KAAMhD,EACN,MAAOyB,EAAE,sBAAsB,EAM/B,YACEgB,EAAWgB,MAAMR,SACf,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,UAAU,mBACV,QAAQ,EAAA,EAAA,IAAA,CAAC,EAAD,CAAM,GAAG,kBAAkB,CAAA,WAElCxB,EAAE,2BAA2B,CACxB,CAAA,EACNmF,IAAAA,GAEN,YAAanF,EAAE,4BAA4B,EAC3C,GAAIyE,EAAS,UAAU,EACvB,KAAK,UAAU,CAAA,GAGjB,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,UAAU,SACV,OAAO,UACP,QAASrC,EAAcgD,UACvB,aAAcpF,EAAE,kBAAkB,EAClC,KAAK,SACL,OAAO,cAENA,EAAE,cAAc,CACT,CAAA,CACN,IAGPiB,IACC,EAAA,EAAA,IAAA,CAAC,MAAD,CACE,UAAU,6FACV,wBAAyB,CAAEoE,OAAQpE,CAAe,CAAE,CAAA,GAItDD,EAAWE,aAAaoE,qBACxB3D,KACA,EAAA,EAAA,KAAA,CAAC,EAAD,CAAA,SAAA,CACGX,EAAWE,aAAaoE,sBACvB,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAM,OAAQ/F,EAAmBa,CAAM,EAAG,GAAG,qBAC1CJ,EAAE,qBAAqB,CACpB,CAAA,EAER,KAAMA,EAAE,wBAAwB,CAAE,CAAA,EAGrC2B,IACC,EAAA,EAAA,IAAA,CAAC,EAAD,CACE,MACE,EAAA,EAAA,IAAA,CAAC,EAAD,CAAM,OAAQpC,EAAmBa,CAAM,EAAG,GAAG,kBAC1CJ,EAAE,8BAA8B,CAC7B,CAAA,CACP,CAAA,CAGK,CAAA,CAAA,CAEJ,CAAA,CAAA,CAEhB"}
|
|
1
|
+
{"version":3,"file":"password-6QOqTV_s.js","names":["standardSchemaResolver","useMutation","useQueryClient","useSuspenseQuery","Link","useRouter","TRButton","TRLink","LockIcon","MailIcon","useCallback","useEffect","useMemo","useRef","useForm","useTranslation","z","AuthField","AuthFooter","AuthFooterLink","AuthPageHeader","AuthorizationContextBanner","AuthLayout","buildAuthenticatedAuthorizeUrl","extractOAuthParams","isOAuthFlow","tick","appConfigQueryOptions","loginMutationOptions","startConditionalPasskeyAuth","getSessionQueryOptions","Route","LoginPassword","t","i18n","router","queryClient","search","useSearch","authorizeSearch","OAuthSearch","account_selection_state","prompt","split","includes","account_selected","lang","language","data","configData","implicitNotice","registration","signup_notice","fallback_language","isPasswordAuthEnabled","auth","password","enabled","isPasskeyEnabled","passkey","hasMultipleLoginMethods","identity_providers","length","loginSchema","object","email","string","min","pipe","loginMutation","onSuccess","params","user","email_verification_required","email_verified","navigate","to","setQueryData","queryKey","registered_2fa_methods","SecondFactorMethod","totp","totp_registered","push","passkey_count","second_factor_required","available_2fa_setup_methods","method","passkey_name","window","location","href","onError","_error","setError","type","message","onSettled","invalidateQueries","handlePasskeySuccess","AuthResponse","abortControllerRef","AbortController","current","signal","abort","register","handleSubmit","formState","errors","defaultValues","resolver","onSubmit","values","infer","mutate","undefined","isPending","__html","public_registration","component"],"sources":["../../../frontend/src/queries/login.ts","../../../frontend/src/routes/login/password/index.tsx?tsr-split=component"],"sourcesContent":["import { mutationOptions } from '@tanstack/react-query';\nimport type { InferRequestType, InferResponseType } from 'hono/client';\nimport { client, jsonOk } from '#frontend/libs/api.ts';\n\nexport type LoginParams = InferRequestType<\n (typeof client.api.auth.login)['$post']\n>['json'];\n\nexport type LoginResponse = InferResponseType<\n (typeof client.api.auth.login)['$post'],\n 200\n>;\n\nexport const loginMutationOptions = mutationOptions({\n mutationFn: async (values: LoginParams) => {\n const res = await client.api.auth.login.$post({\n json: values,\n });\n return jsonOk(res);\n },\n});\n","import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLink } from '@tinyrack/ui/components/link';\nimport { LockIcon, MailIcon } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.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 type OAuthSearch,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { loginMutationOptions } from '#frontend/queries/login.ts';\nimport { startConditionalPasskeyAuth } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/login/password/')({\n component: LoginPassword,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction LoginPassword() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const authorizeSearch: OAuthSearch =\n search.account_selection_state &&\n search.prompt?.split(' ').includes('login')\n ? { ...search, account_selected: 1 }\n : search;\n const lang = search.lang ?? i18n.language;\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const hasMultipleLoginMethods =\n configData.identity_providers.length +\n (isPasswordAuthEnabled ? 1 : 0) +\n (isPasskeyEnabled ? 1 : 0) >\n 1;\n\n const loginSchema = useMemo(\n () =>\n z.object({\n email: z\n .string()\n .min(1, t('validation.email.required'))\n .pipe(z.email(t('validation.email.invalid'))),\n password: z.string().min(1, t('validation.password.required')),\n }),\n [t],\n );\n\n const loginMutation = useMutation({\n ...loginMutationOptions,\n onSuccess: async (data, params) => {\n const user = data.user;\n\n if (user.email_verification_required && !user.email_verified) {\n router.navigate({\n to: '/verify/email',\n search: {\n email: params.email,\n ...extractOAuthParams(search),\n },\n });\n return;\n }\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n const registered_2fa_methods: SecondFactorMethod[] = [];\n if (configData.auth.password.totp.enabled && user.totp_registered) {\n registered_2fa_methods.push('totp');\n }\n if (configData.auth.passkey.enabled && user.passkey_count > 0) {\n registered_2fa_methods.push('passkey');\n }\n\n if (user.second_factor_required && registered_2fa_methods.length === 0) {\n if (configData.available_2fa_setup_methods.length > 1) {\n return router.navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n } else if (configData.available_2fa_setup_methods.length === 1) {\n const method = configData.available_2fa_setup_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/setup/passkey',\n search: {\n ...extractOAuthParams(search),\n passkey_name: 'default',\n },\n });\n }\n }\n }\n\n if (registered_2fa_methods.length > 1) {\n return router.navigate({\n to: '/verify/2fa',\n search: extractOAuthParams(search),\n });\n } else if (registered_2fa_methods.length === 1) {\n const method = registered_2fa_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/verify/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/verify/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n if (isOAuthFlow(search)) {\n window.location.href =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n return router.navigate({ to: '/profile' });\n }\n }\n },\n onError: (_error) => {\n setError('email', {\n type: 'manual',\n message: t('login.error.failed'),\n });\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const handlePasskeySuccess = useCallback(\n 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 =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n [queryClient, router, search, authorizeSearch],\n );\n\n // Conditional UI: Start passkey autofill on page load\n const abortControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n if (!isPasskeyEnabled || !isPasswordAuthEnabled) {\n return;\n }\n\n abortControllerRef.current = new AbortController();\n startConditionalPasskeyAuth(\n handlePasskeySuccess,\n abortControllerRef.current.signal,\n );\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [isPasskeyEnabled, isPasswordAuthEnabled, handlePasskeySuccess]);\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm({\n defaultValues: {\n email: '',\n password: '',\n },\n resolver: standardSchemaResolver(loginSchema),\n });\n\n const onSubmit = async (values: z.infer<typeof loginSchema>) => {\n loginMutation.mutate(values);\n };\n\n return (\n <AuthLayout>\n {/* Branding lives in the layout's brand panel — see /login. */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {isPasswordAuthEnabled && (\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n autoComplete=\"username webauthn\"\n error={errors.email}\n icon={MailIcon}\n label={t('login.email.label')}\n placeholder={t('login.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <AuthField\n autoComplete=\"current-password\"\n error={errors.password}\n icon={LockIcon}\n label={t('login.password.label')}\n /*\n * On the label row rather than under the field: it belongs to the\n * password, and putting it between the field and the submit button\n * separated the two things the user is actually trying to do.\n */\n labelAction={\n configData.email.enabled ? (\n <TRLink\n className=\"text-tinyrack-xs\"\n render={<Link to=\"/password/forgot\" />}\n >\n {t('login.link.forgotPassword')}\n </TRLink>\n ) : undefined\n }\n placeholder={t('login.password.placeholder')}\n {...register('password')}\n type=\"password\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={loginMutation.isPending}\n loadingLabel={t('login.submitting')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('login.submit')}\n </TRButton>\n </form>\n )}\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {(configData.registration.public_registration ||\n hasMultipleLoginMethods) && (\n <AuthFooter>\n {configData.registration.public_registration && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/register\">\n {t('login.link.register')}\n </Link>\n }\n text={t('login.footer.noAccount')}\n />\n )}\n {hasMultipleLoginMethods && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('login.password.backToMethods')}\n </Link>\n }\n />\n )}\n </AuthFooter>\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{G as e,J as t,Z as n,c as r,i,n as a,q as o,v as s}from"./createLucideIcon-BPpAIXR_.js";import{bn as c,bt as l,st as u}from"./config-DTQCUlfZ.js";import{
|
|
2
|
-
//# sourceMappingURL=password-strength-
|
|
1
|
+
import{G as e,J as t,Z as n,c as r,i,n as a,q as o,v as s}from"./createLucideIcon-BPpAIXR_.js";import{bn as c,bt as l,st as u}from"./config-DTQCUlfZ.js";import{g as d}from"./shell-header-bar-BPgCjFPv.js";import{n as f,t as p}from"./valueToPercent-LgbY_prH.js";var m=n(t(),1),h=m.createContext(void 0);function g(){let e=m.useContext(h);if(e===void 0)throw Error(s(38));return e}var _=o(),v=m.forwardRef(function(e,t){let{format:n,getAriaValueText:i,locale:a,max:o=100,min:s=0,value:c,render:d,className:g,children:v,style:y,...b}=e,[x,S]=m.useState(),C=p(c,s,o),w=u(Number.isNaN(C)?0:C,0,100),T=u(Number.isNaN(c)?s:c,s,o),E=n?f(T,a,n):f(w/100,a,{style:`percent`}),D=E;i&&(D=i(E,c));let O={"aria-labelledby":x,"aria-valuemax":o,"aria-valuemin":s,"aria-valuenow":T,"aria-valuetext":D,role:`meter`,children:(0,_.jsxs)(m.Fragment,{children:[v,(0,_.jsx)(`span`,{role:`presentation`,style:l,children:`x`})]})},k=m.useMemo(()=>({formattedValue:E,percentageValue:w,setLabelId:S,value:c}),[E,w,S,c]),A=r(`div`,e,{ref:t,props:[O,b]});return(0,_.jsx)(h.Provider,{value:k,children:A})}),y=m.forwardRef(function(e,t){let{render:n,className:i,style:a,...o}=e;return r(`div`,e,{ref:t,props:o})}),b=m.forwardRef(function(e,t){let{render:n,className:i,style:a,...o}=e,{percentageValue:s}=g();return r(`div`,e,{ref:t,props:[{style:{insetInlineStart:0,height:`inherit`,width:`${s}%`}},o]})}),x=m.forwardRef(function(e,t){let{className:n,render:i,children:a,style:o,...s}=e,{value:c,formattedValue:l}=g();return r(`span`,e,{ref:t,props:[{"aria-hidden":!0,children:typeof a==`function`?a(l,c):l},s]})}),S=m.forwardRef(function(e,t){let{render:n,className:i,style:a,id:o,...s}=e,{setLabelId:c}=g();return r(`span`,e,{ref:t,props:[{id:d(o,c),role:`presentation`},s]})}),C=c(b,`tr-meter-indicator`),w=c(S,`tr-meter-label`);function T({className:t,max:n=100,min:r=0,value:i,variant:a=`neutral`,...o}){let s=Math.min(n,Math.max(r,Number.isNaN(i)?r:i));return(0,_.jsx)(v,{...o,className:e(`tr-meter`,t),"data-variant":a,max:n,min:r,value:s})}var E={Root:T,Track:c(y,`tr-meter-track`),Indicator:C,Value:c(x,`tr-meter-value`),Label:w},D=[`weak`,`fair`,`good`,`strong`],O={weak:`danger`,fair:`warning`,good:`info`,strong:`success`};function k(e,t){if(e.length===0)return 0;if(e.length<t.min_length)return 1;let n=[/[a-z]/,/[A-Z]/,/\d/,/[^a-zA-Z0-9]/].filter(t=>t.test(e)).length,r=e.length>=t.min_length+4;return n>=3&&r?4:n>=2&&r?3:2}function A({password:e,policy:t}){let{t:n}=i(),r=k(e,t);if(r===0)return null;let o=D[r-1];return(0,_.jsxs)(E.Root,{"aria-live":`polite`,className:`flex flex-col gap-tinyrack-xs`,max:4,min:0,value:r,variant:O[o],children:[(0,_.jsxs)(`div`,{className:`flex items-baseline justify-between gap-tinyrack-sm`,children:[(0,_.jsx)(E.Label,{render:(0,_.jsx)(a,{color:`muted`,variant:`caption`,children:n(`validation.password.strength.label`)})}),(0,_.jsx)(E.Value,{render:(0,_.jsx)(a,{color:`muted`,variant:`caption`,children:n(`validation.password.strength.${o}`)})})]}),(0,_.jsx)(E.Track,{children:(0,_.jsx)(E.Indicator,{})})]})}export{A as t};
|
|
2
|
+
//# sourceMappingURL=password-strength-DtGMHOD-.js.map
|
package/public/assets/{password-strength-BeSOu_Ga.js.map → password-strength-DtGMHOD-.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-strength-BeSOu_Ga.js","names":["_formatErrorMessage","Meter.Indicator","Meter.Label","Meter.Root","Meter.Track","Meter.Value"],"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/meter/root/MeterRootContext.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/meter/root/MeterRoot.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/meter/track/MeterTrack.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/meter/indicator/MeterIndicator.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/meter/value/MeterValue.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/meter/label/MeterLabel.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/meter/meter-indicator.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/meter/meter-label.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/meter/meter-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/meter/meter-track.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/meter/meter-value.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/meter/index.js","../../../frontend/src/components/auth/password-strength.tsx"],"sourcesContent":["'use client';\n\nimport _formatErrorMessage from \"@base-ui/utils/formatErrorMessage\";\nimport * as React from 'react';\nexport const MeterRootContext = /*#__PURE__*/React.createContext(undefined);\nif (process.env.NODE_ENV !== \"production\") MeterRootContext.displayName = \"MeterRootContext\";\nexport function useMeterRootContext() {\n const context = React.useContext(MeterRootContext);\n if (context === undefined) {\n throw new Error(process.env.NODE_ENV !== \"production\" ? 'Base UI: MeterRootContext is missing. Meter parts must be placed within <Meter.Root>.' : _formatErrorMessage(38));\n }\n return context;\n}","'use client';\n\nimport * as React from 'react';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { MeterRootContext } from \"./MeterRootContext.mjs\";\nimport { formatNumber } from \"../../utils/formatNumber.mjs\";\nimport { valueToPercent } from \"../../utils/valueToPercent.mjs\";\nimport { clamp } from \"../../internals/clamp.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * Groups all parts of the meter and provides the value for screen readers.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nimport { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const MeterRoot = /*#__PURE__*/React.forwardRef(function MeterRoot(componentProps, forwardedRef) {\n const {\n format,\n getAriaValueText,\n locale,\n max = 100,\n min = 0,\n value: valueProp,\n render,\n className,\n children,\n style,\n ...elementProps\n } = componentProps;\n const [labelId, setLabelId] = React.useState();\n\n // `clamp` handles infinity, but NaN needs an explicit fallback before normalizing range outputs.\n const rawPercentage = valueToPercent(valueProp, min, max);\n const percentageValue = clamp(Number.isNaN(rawPercentage) ? 0 : rawPercentage, 0, 100);\n const clampedValue = clamp(Number.isNaN(valueProp) ? min : valueProp, min, max);\n\n // Format the clamped value so visible and accessible text stay in sync with `aria-valuenow` and\n // the indicator fill. The raw value remains available as the second `getAriaValueText` argument.\n const formattedValue = format ? formatNumber(clampedValue, locale, format) : formatNumber(percentageValue / 100, locale, {\n style: 'percent'\n });\n let ariaValuetext = formattedValue;\n if (getAriaValueText) {\n ariaValuetext = getAriaValueText(formattedValue, valueProp);\n }\n const defaultProps = {\n 'aria-labelledby': labelId,\n 'aria-valuemax': max,\n 'aria-valuemin': min,\n 'aria-valuenow': clampedValue,\n 'aria-valuetext': ariaValuetext,\n role: 'meter',\n children: /*#__PURE__*/_jsxs(React.Fragment, {\n children: [children, /*#__PURE__*/_jsx(\"span\", {\n role: \"presentation\",\n style: visuallyHidden,\n children: \"x\"\n })]\n })\n };\n const contextValue = React.useMemo(() => ({\n formattedValue,\n percentageValue,\n setLabelId,\n value: valueProp\n }), [formattedValue, percentageValue, setLabelId, valueProp]);\n const element = useRenderElement('div', componentProps, {\n ref: forwardedRef,\n props: [defaultProps, elementProps]\n });\n return /*#__PURE__*/_jsx(MeterRootContext.Provider, {\n value: contextValue,\n children: element\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterRoot.displayName = \"MeterRoot\";","'use client';\n\nimport * as React from 'react';\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * Contains the meter indicator and represents the entire range of the meter.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterTrack = /*#__PURE__*/React.forwardRef(function MeterTrack(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n ...elementProps\n } = componentProps;\n return useRenderElement('div', componentProps, {\n ref: forwardedRef,\n props: elementProps\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterTrack.displayName = \"MeterTrack\";","'use client';\n\nimport * as React from 'react';\nimport { useMeterRootContext } from \"../root/MeterRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * Visualizes the position of the value along the range.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterIndicator = /*#__PURE__*/React.forwardRef(function MeterIndicator(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n ...elementProps\n } = componentProps;\n const {\n percentageValue\n } = useMeterRootContext();\n return useRenderElement('div', componentProps, {\n ref: forwardedRef,\n props: [{\n style: {\n insetInlineStart: 0,\n height: 'inherit',\n width: `${percentageValue}%`\n }\n }, elementProps]\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterIndicator.displayName = \"MeterIndicator\";","'use client';\n\nimport * as React from 'react';\nimport { useMeterRootContext } from \"../root/MeterRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * A text element displaying the current value.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterValue = /*#__PURE__*/React.forwardRef(function MeterValue(componentProps, forwardedRef) {\n const {\n className,\n render,\n children,\n style,\n ...elementProps\n } = componentProps;\n const {\n value,\n formattedValue\n } = useMeterRootContext();\n return useRenderElement('span', componentProps, {\n ref: forwardedRef,\n props: [{\n 'aria-hidden': true,\n children: typeof children === 'function' ? children(formattedValue, value) : formattedValue\n }, elementProps]\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterValue.displayName = \"MeterValue\";","'use client';\n\nimport * as React from 'react';\nimport { useMeterRootContext } from \"../root/MeterRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\nimport { useRegisteredLabelId } from \"../../utils/useRegisteredLabelId.mjs\";\n\n/**\n * An accessible label for the meter.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterLabel = /*#__PURE__*/React.forwardRef(function MeterLabel(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n id: idProp,\n ...elementProps\n } = componentProps;\n const {\n setLabelId\n } = useMeterRootContext();\n const id = useRegisteredLabelId(idProp, setLabelId);\n return useRenderElement('span', componentProps, {\n ref: forwardedRef,\n props: [{\n id,\n role: 'presentation'\n }, elementProps]\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterLabel.displayName = \"MeterLabel\";","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-indicator.tsx\nconst TRMeterIndicator = createComponentPart(Meter.Indicator, \"tr-meter-indicator\");\n//#endregion\nexport { TRMeterIndicator };\n\n//# sourceMappingURL=meter-indicator.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-label.tsx\nconst TRMeterLabel = createComponentPart(Meter.Label, \"tr-meter-label\");\n//#endregion\nexport { TRMeterLabel };\n\n//# sourceMappingURL=meter-label.js.map","\"use client\";\nimport { mergeComponentClassName } from \"../../internal/component-class-name.js\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-root.tsx\nfunction TRMeterRoot({ className, max = 100, min = 0, value, variant = \"neutral\", ...props }) {\n\tconst clampedValue = Math.min(max, Math.max(min, Number.isNaN(value) ? min : value));\n\treturn /* @__PURE__ */ jsx(Meter.Root, {\n\t\t...props,\n\t\tclassName: mergeComponentClassName(\"tr-meter\", className),\n\t\t\"data-variant\": variant,\n\t\tmax,\n\t\tmin,\n\t\tvalue: clampedValue\n\t});\n}\n//#endregion\nexport { TRMeterRoot };\n\n//# sourceMappingURL=meter-root.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-track.tsx\nconst TRMeterTrack = createComponentPart(Meter.Track, \"tr-meter-track\");\n//#endregion\nexport { TRMeterTrack };\n\n//# sourceMappingURL=meter-track.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-value.tsx\nconst TRMeterValue = createComponentPart(Meter.Value, \"tr-meter-value\");\n//#endregion\nexport { TRMeterValue };\n\n//# sourceMappingURL=meter-value.js.map","import { TRMeterIndicator } from \"./meter-indicator.js\";\nimport { TRMeterLabel } from \"./meter-label.js\";\nimport { TRMeterRoot } from \"./meter-root.js\";\nimport { TRMeterTrack } from \"./meter-track.js\";\nimport { TRMeterValue } from \"./meter-value.js\";\n//#region src/components/meter/index.tsx\nconst TRMeter = {\n\tRoot: TRMeterRoot,\n\tTrack: TRMeterTrack,\n\tIndicator: TRMeterIndicator,\n\tValue: TRMeterValue,\n\tLabel: TRMeterLabel\n};\n//#endregion\nexport { TRMeter, TRMeterIndicator, TRMeterLabel, TRMeterRoot, TRMeterTrack, TRMeterValue };\n\n//# sourceMappingURL=index.js.map","import { TRMeter } from '@tinyrack/ui/components/meter';\nimport { TRText } from '@tinyrack/ui/components/text';\nimport { useTranslation } from 'react-i18next';\n\ntype PasswordPolicy = {\n min_length: number;\n max_length: number;\n};\n\ntype PasswordStrengthProps = {\n password: string;\n policy: PasswordPolicy;\n};\n\nconst LEVELS = ['weak', 'fair', 'good', 'strong'] as const;\n\nconst VARIANTS = {\n weak: 'danger',\n fair: 'warning',\n good: 'info',\n strong: 'success',\n} as const;\n\n/**\n * Scores a password 1-4 for feedback only.\n *\n * Deliberately simple: length against the deployment's own policy plus\n * character-class variety. The server is the authority on what is acceptable,\n * so this only needs to steer the user, and a full entropy estimator would be\n * a large dependency for a hint bar.\n *\n * Returns 0 for \"nothing to say yet\" so the caller can stay silent.\n */\nfunction scorePassword(password: string, policy: PasswordPolicy): number {\n if (password.length === 0) {\n return 0;\n }\n if (password.length < policy.min_length) {\n return 1;\n }\n\n const classes = [/[a-z]/, /[A-Z]/, /\\d/, /[^a-zA-Z0-9]/].filter((pattern) =>\n pattern.test(password),\n ).length;\n\n const isLong = password.length >= policy.min_length + 4;\n\n if (classes >= 3 && isLong) {\n return 4;\n }\n if (classes >= 2 && isLong) {\n return 3;\n }\n return 2;\n}\n\n/**\n * Strength hint under a new-password field.\n *\n * Renders nothing for an empty field so the form does not shift as soon as it\n * is focused. Announced politely rather than assertively — it updates on every\n * keystroke and should not interrupt typing.\n */\nexport function PasswordStrength({ password, policy }: PasswordStrengthProps) {\n const { t } = useTranslation();\n const score = scorePassword(password, policy);\n\n if (score === 0) {\n return null;\n }\n\n const level = LEVELS[score - 1];\n\n return (\n <TRMeter.Root\n aria-live=\"polite\"\n className=\"flex flex-col gap-tinyrack-xs\"\n max={4}\n min={0}\n value={score}\n variant={VARIANTS[level]}\n >\n <div className=\"flex items-baseline justify-between gap-tinyrack-sm\">\n <TRMeter.Label\n render={\n <TRText color=\"muted\" variant=\"caption\">\n {t('validation.password.strength.label')}\n </TRText>\n }\n />\n <TRMeter.Value\n render={\n <TRText color=\"muted\" variant=\"caption\">\n {t(`validation.password.strength.${level}`)}\n </TRText>\n }\n />\n </div>\n <TRMeter.Track>\n <TRMeter.Indicator />\n </TRMeter.Track>\n </TRMeter.Root>\n );\n}\n"],"x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"password-strength-DtGMHOD-.js","names":["_formatErrorMessage","Meter.Indicator","Meter.Label","Meter.Root","Meter.Track","Meter.Value"],"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/meter/root/MeterRootContext.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/meter/root/MeterRoot.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/meter/track/MeterTrack.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/meter/indicator/MeterIndicator.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/meter/value/MeterValue.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/meter/label/MeterLabel.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/meter/meter-indicator.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/meter/meter-label.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/meter/meter-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/meter/meter-track.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/meter/meter-value.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/meter/index.js","../../../frontend/src/components/auth/password-strength.tsx"],"sourcesContent":["'use client';\n\nimport _formatErrorMessage from \"@base-ui/utils/formatErrorMessage\";\nimport * as React from 'react';\nexport const MeterRootContext = /*#__PURE__*/React.createContext(undefined);\nif (process.env.NODE_ENV !== \"production\") MeterRootContext.displayName = \"MeterRootContext\";\nexport function useMeterRootContext() {\n const context = React.useContext(MeterRootContext);\n if (context === undefined) {\n throw new Error(process.env.NODE_ENV !== \"production\" ? 'Base UI: MeterRootContext is missing. Meter parts must be placed within <Meter.Root>.' : _formatErrorMessage(38));\n }\n return context;\n}","'use client';\n\nimport * as React from 'react';\nimport { visuallyHidden } from '@base-ui/utils/visuallyHidden';\nimport { MeterRootContext } from \"./MeterRootContext.mjs\";\nimport { formatNumber } from \"../../utils/formatNumber.mjs\";\nimport { valueToPercent } from \"../../utils/valueToPercent.mjs\";\nimport { clamp } from \"../../internals/clamp.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * Groups all parts of the meter and provides the value for screen readers.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nimport { jsx as _jsx, jsxs as _jsxs } from \"react/jsx-runtime\";\nexport const MeterRoot = /*#__PURE__*/React.forwardRef(function MeterRoot(componentProps, forwardedRef) {\n const {\n format,\n getAriaValueText,\n locale,\n max = 100,\n min = 0,\n value: valueProp,\n render,\n className,\n children,\n style,\n ...elementProps\n } = componentProps;\n const [labelId, setLabelId] = React.useState();\n\n // `clamp` handles infinity, but NaN needs an explicit fallback before normalizing range outputs.\n const rawPercentage = valueToPercent(valueProp, min, max);\n const percentageValue = clamp(Number.isNaN(rawPercentage) ? 0 : rawPercentage, 0, 100);\n const clampedValue = clamp(Number.isNaN(valueProp) ? min : valueProp, min, max);\n\n // Format the clamped value so visible and accessible text stay in sync with `aria-valuenow` and\n // the indicator fill. The raw value remains available as the second `getAriaValueText` argument.\n const formattedValue = format ? formatNumber(clampedValue, locale, format) : formatNumber(percentageValue / 100, locale, {\n style: 'percent'\n });\n let ariaValuetext = formattedValue;\n if (getAriaValueText) {\n ariaValuetext = getAriaValueText(formattedValue, valueProp);\n }\n const defaultProps = {\n 'aria-labelledby': labelId,\n 'aria-valuemax': max,\n 'aria-valuemin': min,\n 'aria-valuenow': clampedValue,\n 'aria-valuetext': ariaValuetext,\n role: 'meter',\n children: /*#__PURE__*/_jsxs(React.Fragment, {\n children: [children, /*#__PURE__*/_jsx(\"span\", {\n role: \"presentation\",\n style: visuallyHidden,\n children: \"x\"\n })]\n })\n };\n const contextValue = React.useMemo(() => ({\n formattedValue,\n percentageValue,\n setLabelId,\n value: valueProp\n }), [formattedValue, percentageValue, setLabelId, valueProp]);\n const element = useRenderElement('div', componentProps, {\n ref: forwardedRef,\n props: [defaultProps, elementProps]\n });\n return /*#__PURE__*/_jsx(MeterRootContext.Provider, {\n value: contextValue,\n children: element\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterRoot.displayName = \"MeterRoot\";","'use client';\n\nimport * as React from 'react';\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * Contains the meter indicator and represents the entire range of the meter.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterTrack = /*#__PURE__*/React.forwardRef(function MeterTrack(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n ...elementProps\n } = componentProps;\n return useRenderElement('div', componentProps, {\n ref: forwardedRef,\n props: elementProps\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterTrack.displayName = \"MeterTrack\";","'use client';\n\nimport * as React from 'react';\nimport { useMeterRootContext } from \"../root/MeterRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * Visualizes the position of the value along the range.\n * Renders a `<div>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterIndicator = /*#__PURE__*/React.forwardRef(function MeterIndicator(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n ...elementProps\n } = componentProps;\n const {\n percentageValue\n } = useMeterRootContext();\n return useRenderElement('div', componentProps, {\n ref: forwardedRef,\n props: [{\n style: {\n insetInlineStart: 0,\n height: 'inherit',\n width: `${percentageValue}%`\n }\n }, elementProps]\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterIndicator.displayName = \"MeterIndicator\";","'use client';\n\nimport * as React from 'react';\nimport { useMeterRootContext } from \"../root/MeterRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\n\n/**\n * A text element displaying the current value.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterValue = /*#__PURE__*/React.forwardRef(function MeterValue(componentProps, forwardedRef) {\n const {\n className,\n render,\n children,\n style,\n ...elementProps\n } = componentProps;\n const {\n value,\n formattedValue\n } = useMeterRootContext();\n return useRenderElement('span', componentProps, {\n ref: forwardedRef,\n props: [{\n 'aria-hidden': true,\n children: typeof children === 'function' ? children(formattedValue, value) : formattedValue\n }, elementProps]\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterValue.displayName = \"MeterValue\";","'use client';\n\nimport * as React from 'react';\nimport { useMeterRootContext } from \"../root/MeterRootContext.mjs\";\nimport { useRenderElement } from \"../../internals/useRenderElement.mjs\";\nimport { useRegisteredLabelId } from \"../../utils/useRegisteredLabelId.mjs\";\n\n/**\n * An accessible label for the meter.\n * Renders a `<span>` element.\n *\n * Documentation: [Base UI Meter](https://base-ui.com/react/components/meter)\n */\nexport const MeterLabel = /*#__PURE__*/React.forwardRef(function MeterLabel(componentProps, forwardedRef) {\n const {\n render,\n className,\n style,\n id: idProp,\n ...elementProps\n } = componentProps;\n const {\n setLabelId\n } = useMeterRootContext();\n const id = useRegisteredLabelId(idProp, setLabelId);\n return useRenderElement('span', componentProps, {\n ref: forwardedRef,\n props: [{\n id,\n role: 'presentation'\n }, elementProps]\n });\n});\nif (process.env.NODE_ENV !== \"production\") MeterLabel.displayName = \"MeterLabel\";","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-indicator.tsx\nconst TRMeterIndicator = createComponentPart(Meter.Indicator, \"tr-meter-indicator\");\n//#endregion\nexport { TRMeterIndicator };\n\n//# sourceMappingURL=meter-indicator.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-label.tsx\nconst TRMeterLabel = createComponentPart(Meter.Label, \"tr-meter-label\");\n//#endregion\nexport { TRMeterLabel };\n\n//# sourceMappingURL=meter-label.js.map","\"use client\";\nimport { mergeComponentClassName } from \"../../internal/component-class-name.js\";\nimport { jsx } from \"react/jsx-runtime\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-root.tsx\nfunction TRMeterRoot({ className, max = 100, min = 0, value, variant = \"neutral\", ...props }) {\n\tconst clampedValue = Math.min(max, Math.max(min, Number.isNaN(value) ? min : value));\n\treturn /* @__PURE__ */ jsx(Meter.Root, {\n\t\t...props,\n\t\tclassName: mergeComponentClassName(\"tr-meter\", className),\n\t\t\"data-variant\": variant,\n\t\tmax,\n\t\tmin,\n\t\tvalue: clampedValue\n\t});\n}\n//#endregion\nexport { TRMeterRoot };\n\n//# sourceMappingURL=meter-root.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-track.tsx\nconst TRMeterTrack = createComponentPart(Meter.Track, \"tr-meter-track\");\n//#endregion\nexport { TRMeterTrack };\n\n//# sourceMappingURL=meter-track.js.map","\"use client\";\nimport { createComponentPart } from \"../../internal/component-part.js\";\nimport { Meter } from \"@base-ui/react/meter\";\n//#region src/components/meter/meter-value.tsx\nconst TRMeterValue = createComponentPart(Meter.Value, \"tr-meter-value\");\n//#endregion\nexport { TRMeterValue };\n\n//# sourceMappingURL=meter-value.js.map","import { TRMeterIndicator } from \"./meter-indicator.js\";\nimport { TRMeterLabel } from \"./meter-label.js\";\nimport { TRMeterRoot } from \"./meter-root.js\";\nimport { TRMeterTrack } from \"./meter-track.js\";\nimport { TRMeterValue } from \"./meter-value.js\";\n//#region src/components/meter/index.tsx\nconst TRMeter = {\n\tRoot: TRMeterRoot,\n\tTrack: TRMeterTrack,\n\tIndicator: TRMeterIndicator,\n\tValue: TRMeterValue,\n\tLabel: TRMeterLabel\n};\n//#endregion\nexport { TRMeter, TRMeterIndicator, TRMeterLabel, TRMeterRoot, TRMeterTrack, TRMeterValue };\n\n//# sourceMappingURL=index.js.map","import { TRMeter } from '@tinyrack/ui/components/meter';\nimport { TRText } from '@tinyrack/ui/components/text';\nimport { useTranslation } from 'react-i18next';\n\ntype PasswordPolicy = {\n min_length: number;\n max_length: number;\n};\n\ntype PasswordStrengthProps = {\n password: string;\n policy: PasswordPolicy;\n};\n\nconst LEVELS = ['weak', 'fair', 'good', 'strong'] as const;\n\nconst VARIANTS = {\n weak: 'danger',\n fair: 'warning',\n good: 'info',\n strong: 'success',\n} as const;\n\n/**\n * Scores a password 1-4 for feedback only.\n *\n * Deliberately simple: length against the deployment's own policy plus\n * character-class variety. The server is the authority on what is acceptable,\n * so this only needs to steer the user, and a full entropy estimator would be\n * a large dependency for a hint bar.\n *\n * Returns 0 for \"nothing to say yet\" so the caller can stay silent.\n */\nfunction scorePassword(password: string, policy: PasswordPolicy): number {\n if (password.length === 0) {\n return 0;\n }\n if (password.length < policy.min_length) {\n return 1;\n }\n\n const classes = [/[a-z]/, /[A-Z]/, /\\d/, /[^a-zA-Z0-9]/].filter((pattern) =>\n pattern.test(password),\n ).length;\n\n const isLong = password.length >= policy.min_length + 4;\n\n if (classes >= 3 && isLong) {\n return 4;\n }\n if (classes >= 2 && isLong) {\n return 3;\n }\n return 2;\n}\n\n/**\n * Strength hint under a new-password field.\n *\n * Renders nothing for an empty field so the form does not shift as soon as it\n * is focused. Announced politely rather than assertively — it updates on every\n * keystroke and should not interrupt typing.\n */\nexport function PasswordStrength({ password, policy }: PasswordStrengthProps) {\n const { t } = useTranslation();\n const score = scorePassword(password, policy);\n\n if (score === 0) {\n return null;\n }\n\n const level = LEVELS[score - 1];\n\n return (\n <TRMeter.Root\n aria-live=\"polite\"\n className=\"flex flex-col gap-tinyrack-xs\"\n max={4}\n min={0}\n value={score}\n variant={VARIANTS[level]}\n >\n <div className=\"flex items-baseline justify-between gap-tinyrack-sm\">\n <TRMeter.Label\n render={\n <TRText color=\"muted\" variant=\"caption\">\n {t('validation.password.strength.label')}\n </TRText>\n }\n />\n <TRMeter.Value\n render={\n <TRText color=\"muted\" variant=\"caption\">\n {t(`validation.password.strength.${level}`)}\n </TRText>\n }\n />\n </div>\n <TRMeter.Track>\n <TRMeter.Indicator />\n </TRMeter.Track>\n </TRMeter.Root>\n );\n}\n"],"x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-C5vUVgr_.js","names":["RouteErrorFallback","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/login/password/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLink } from '@tinyrack/ui/components/link';\nimport { LockIcon, MailIcon } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.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 type OAuthSearch,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { loginMutationOptions } from '#frontend/queries/login.ts';\nimport { startConditionalPasskeyAuth } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/login/password/')({\n component: LoginPassword,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction LoginPassword() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const authorizeSearch: OAuthSearch =\n search.account_selection_state &&\n search.prompt?.split(' ').includes('login')\n ? { ...search, account_selected: 1 }\n : search;\n const lang = search.lang ?? i18n.language;\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const hasMultipleLoginMethods =\n configData.identity_providers.length +\n (isPasswordAuthEnabled ? 1 : 0) +\n (isPasskeyEnabled ? 1 : 0) >\n 1;\n\n const loginSchema = useMemo(\n () =>\n z.object({\n email: z\n .string()\n .min(1, t('validation.email.required'))\n .pipe(z.email(t('validation.email.invalid'))),\n password: z.string().min(1, t('validation.password.required')),\n }),\n [t],\n );\n\n const loginMutation = useMutation({\n ...loginMutationOptions,\n onSuccess: async (data, params) => {\n const user = data.user;\n\n if (user.email_verification_required && !user.email_verified) {\n router.navigate({\n to: '/verify/email',\n search: {\n email: params.email,\n ...extractOAuthParams(search),\n },\n });\n return;\n }\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n const registered_2fa_methods: SecondFactorMethod[] = [];\n if (configData.auth.password.totp.enabled && user.totp_registered) {\n registered_2fa_methods.push('totp');\n }\n if (configData.auth.passkey.enabled && user.passkey_count > 0) {\n registered_2fa_methods.push('passkey');\n }\n\n if (user.second_factor_required && registered_2fa_methods.length === 0) {\n if (configData.available_2fa_setup_methods.length > 1) {\n return router.navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n } else if (configData.available_2fa_setup_methods.length === 1) {\n const method = configData.available_2fa_setup_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/setup/passkey',\n search: {\n ...extractOAuthParams(search),\n passkey_name: 'default',\n },\n });\n }\n }\n }\n\n if (registered_2fa_methods.length > 1) {\n return router.navigate({\n to: '/verify/2fa',\n search: extractOAuthParams(search),\n });\n } else if (registered_2fa_methods.length === 1) {\n const method = registered_2fa_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/verify/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/verify/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n if (isOAuthFlow(search)) {\n window.location.href =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n return router.navigate({ to: '/profile' });\n }\n }\n },\n onError: (_error) => {\n setError('email', {\n type: 'manual',\n message: t('login.error.failed'),\n });\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const handlePasskeySuccess = useCallback(\n 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 =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n [queryClient, router, search, authorizeSearch],\n );\n\n // Conditional UI: Start passkey autofill on page load\n const abortControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n if (!isPasskeyEnabled || !isPasswordAuthEnabled) {\n return;\n }\n\n abortControllerRef.current = new AbortController();\n startConditionalPasskeyAuth(\n handlePasskeySuccess,\n abortControllerRef.current.signal,\n );\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [isPasskeyEnabled, isPasswordAuthEnabled, handlePasskeySuccess]);\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm({\n defaultValues: {\n email: '',\n password: '',\n },\n resolver: standardSchemaResolver(loginSchema),\n });\n\n const onSubmit = async (values: z.infer<typeof loginSchema>) => {\n loginMutation.mutate(values);\n };\n\n return (\n <AuthLayout>\n {/* Branding lives in the layout's brand panel — see /login. */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {isPasswordAuthEnabled && (\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n autoComplete=\"username webauthn\"\n error={errors.email}\n icon={MailIcon}\n label={t('login.email.label')}\n placeholder={t('login.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <AuthField\n autoComplete=\"current-password\"\n error={errors.password}\n icon={LockIcon}\n label={t('login.password.label')}\n /*\n * On the label row rather than under the field: it belongs to the\n * password, and putting it between the field and the submit button\n * separated the two things the user is actually trying to do.\n */\n labelAction={\n configData.email.enabled ? (\n <TRLink\n className=\"text-tinyrack-xs\"\n render={<Link to=\"/password/forgot\" />}\n >\n {t('login.link.forgotPassword')}\n </TRLink>\n ) : undefined\n }\n placeholder={t('login.password.placeholder')}\n {...register('password')}\n type=\"password\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={loginMutation.isPending}\n loadingLabel={t('login.submitting')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('login.submit')}\n </TRButton>\n </form>\n )}\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {(configData.registration.public_registration ||\n hasMultipleLoginMethods) && (\n <AuthFooter>\n {configData.registration.public_registration && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/register\">\n {t('login.link.register')}\n </Link>\n }\n text={t('login.footer.noAccount')}\n />\n )}\n {hasMultipleLoginMethods && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('login.password.backToMethods')}\n </Link>\n }\n />\n )}\n </AuthFooter>\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"wCAqBsF,IAAAC,EAA7ED"}
|
|
1
|
+
{"version":3,"file":"password-tzTSqiOS.js","names":["RouteErrorFallback","SplitErrorComponent","errorComponent"],"sources":["../../../frontend/src/routes/login/password/index.tsx?tsr-split=errorComponent"],"sourcesContent":["import { standardSchemaResolver } from '@hookform/resolvers/standard-schema';\nimport {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from '@tanstack/react-query';\nimport { createFileRoute, Link, useRouter } from '@tanstack/react-router';\nimport { TRButton } from '@tinyrack/ui/components/button';\nimport { TRLink } from '@tinyrack/ui/components/link';\nimport { LockIcon, MailIcon } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useForm } from 'react-hook-form';\nimport { useTranslation } from 'react-i18next';\nimport { z } from 'zod';\nimport { AuthField } from '#frontend/components/auth/auth-field.tsx';\nimport {\n AuthFooter,\n AuthFooterLink,\n} from '#frontend/components/auth/auth-footer.tsx';\nimport { AuthPageHeader } from '#frontend/components/auth/auth-page-header.tsx';\nimport { AuthorizationContextBanner } from '#frontend/components/auth/authorization-context-banner.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 type OAuthSearch,\n OAuthSearchSchema,\n type SecondFactorMethod,\n} from '#frontend/libs/oauth-search.ts';\nimport { tick } from '#frontend/libs/promise.ts';\nimport { getAuthorizationContextQueryOptions } from '#frontend/queries/authorization-context.ts';\nimport { appConfigQueryOptions } from '#frontend/queries/config.ts';\nimport { loginMutationOptions } from '#frontend/queries/login.ts';\nimport { startConditionalPasskeyAuth } from '#frontend/queries/passkey.ts';\nimport {\n type AuthResponse,\n getSessionQueryOptions,\n} from '#frontend/queries/session.ts';\n\nconst SearchSchema = OAuthSearchSchema;\n\nexport const Route = createFileRoute('/login/password/')({\n component: LoginPassword,\n errorComponent: RouteErrorFallback,\n validateSearch: SearchSchema,\n loaderDeps: ({ search }) => ({\n search,\n }),\n loader: async ({ context, deps }) => {\n await context.queryClient.ensureQueryData(appConfigQueryOptions);\n if (hasAuthorizationContext(deps.search)) {\n await context.queryClient.ensureQueryData(\n getAuthorizationContextQueryOptions(deps.search),\n );\n }\n },\n});\n\nfunction LoginPassword() {\n const { t, i18n } = useTranslation();\n const router = useRouter();\n const queryClient = useQueryClient();\n const search = Route.useSearch();\n const authorizeSearch: OAuthSearch =\n search.account_selection_state &&\n search.prompt?.split(' ').includes('login')\n ? { ...search, account_selected: 1 }\n : search;\n const lang = search.lang ?? i18n.language;\n\n const { data: configData } = useSuspenseQuery(appConfigQueryOptions);\n\n const implicitNotice =\n configData.registration.signup_notice?.[lang] ??\n configData.registration.signup_notice?.[configData.i18n.fallback_language];\n const isPasswordAuthEnabled = configData.auth.password.enabled;\n const isPasskeyEnabled = configData.auth.passkey.enabled;\n const hasMultipleLoginMethods =\n configData.identity_providers.length +\n (isPasswordAuthEnabled ? 1 : 0) +\n (isPasskeyEnabled ? 1 : 0) >\n 1;\n\n const loginSchema = useMemo(\n () =>\n z.object({\n email: z\n .string()\n .min(1, t('validation.email.required'))\n .pipe(z.email(t('validation.email.invalid'))),\n password: z.string().min(1, t('validation.password.required')),\n }),\n [t],\n );\n\n const loginMutation = useMutation({\n ...loginMutationOptions,\n onSuccess: async (data, params) => {\n const user = data.user;\n\n if (user.email_verification_required && !user.email_verified) {\n router.navigate({\n to: '/verify/email',\n search: {\n email: params.email,\n ...extractOAuthParams(search),\n },\n });\n return;\n }\n\n queryClient.setQueryData(getSessionQueryOptions.queryKey, {\n user: user,\n });\n await tick();\n\n const registered_2fa_methods: SecondFactorMethod[] = [];\n if (configData.auth.password.totp.enabled && user.totp_registered) {\n registered_2fa_methods.push('totp');\n }\n if (configData.auth.passkey.enabled && user.passkey_count > 0) {\n registered_2fa_methods.push('passkey');\n }\n\n if (user.second_factor_required && registered_2fa_methods.length === 0) {\n if (configData.available_2fa_setup_methods.length > 1) {\n return router.navigate({\n to: '/setup/2fa',\n search: extractOAuthParams(search),\n });\n } else if (configData.available_2fa_setup_methods.length === 1) {\n const method = configData.available_2fa_setup_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/setup/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/setup/passkey',\n search: {\n ...extractOAuthParams(search),\n passkey_name: 'default',\n },\n });\n }\n }\n }\n\n if (registered_2fa_methods.length > 1) {\n return router.navigate({\n to: '/verify/2fa',\n search: extractOAuthParams(search),\n });\n } else if (registered_2fa_methods.length === 1) {\n const method = registered_2fa_methods[0];\n if (method === 'totp') {\n return router.navigate({\n to: '/verify/totp',\n search: extractOAuthParams(search),\n });\n } else {\n return router.navigate({\n to: '/verify/passkey',\n search: extractOAuthParams(search),\n });\n }\n } else {\n if (isOAuthFlow(search)) {\n window.location.href =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n return router.navigate({ to: '/profile' });\n }\n }\n },\n onError: (_error) => {\n setError('email', {\n type: 'manual',\n message: t('login.error.failed'),\n });\n },\n onSettled: () => {\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n });\n\n const handlePasskeySuccess = useCallback(\n 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 =\n buildAuthenticatedAuthorizeUrl(authorizeSearch);\n } else {\n router.navigate({ to: '/profile' });\n }\n }\n queryClient.invalidateQueries({\n queryKey: getSessionQueryOptions.queryKey,\n });\n },\n [queryClient, router, search, authorizeSearch],\n );\n\n // Conditional UI: Start passkey autofill on page load\n const abortControllerRef = useRef<AbortController | null>(null);\n\n useEffect(() => {\n if (!isPasskeyEnabled || !isPasswordAuthEnabled) {\n return;\n }\n\n abortControllerRef.current = new AbortController();\n startConditionalPasskeyAuth(\n handlePasskeySuccess,\n abortControllerRef.current.signal,\n );\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [isPasskeyEnabled, isPasswordAuthEnabled, handlePasskeySuccess]);\n\n const {\n register,\n setError,\n handleSubmit,\n formState: { errors },\n } = useForm({\n defaultValues: {\n email: '',\n password: '',\n },\n resolver: standardSchemaResolver(loginSchema),\n });\n\n const onSubmit = async (values: z.infer<typeof loginSchema>) => {\n loginMutation.mutate(values);\n };\n\n return (\n <AuthLayout>\n {/* Branding lives in the layout's brand panel — see /login. */}\n <AuthPageHeader\n subtitle={t('login.selectMethod.subtitle') || undefined}\n title={t('login.title')}\n />\n\n <AuthorizationContextBanner search={search} />\n\n {isPasswordAuthEnabled && (\n <form\n className=\"flex flex-col gap-tinyrack-lg\"\n onSubmit={handleSubmit(onSubmit)}\n >\n <AuthField\n autoComplete=\"username webauthn\"\n error={errors.email}\n icon={MailIcon}\n label={t('login.email.label')}\n placeholder={t('login.email.placeholder')}\n {...register('email')}\n type=\"email\"\n />\n\n <AuthField\n autoComplete=\"current-password\"\n error={errors.password}\n icon={LockIcon}\n label={t('login.password.label')}\n /*\n * On the label row rather than under the field: it belongs to the\n * password, and putting it between the field and the submit button\n * separated the two things the user is actually trying to do.\n */\n labelAction={\n configData.email.enabled ? (\n <TRLink\n className=\"text-tinyrack-xs\"\n render={<Link to=\"/password/forgot\" />}\n >\n {t('login.link.forgotPassword')}\n </TRLink>\n ) : undefined\n }\n placeholder={t('login.password.placeholder')}\n {...register('password')}\n type=\"password\"\n />\n\n <TRButton\n className=\"w-full\"\n intent=\"primary\"\n loading={loginMutation.isPending}\n loadingLabel={t('login.submitting')}\n type=\"submit\"\n uiSize=\"lg\"\n >\n {t('login.submit')}\n </TRButton>\n </form>\n )}\n\n {implicitNotice && (\n <div\n className=\"prose prose-sm text-center text-tinyrack-text-muted text-tinyrack-xs! **:text-tinyrack-xs!\"\n dangerouslySetInnerHTML={{ __html: implicitNotice }}\n />\n )}\n\n {(configData.registration.public_registration ||\n hasMultipleLoginMethods) && (\n <AuthFooter>\n {configData.registration.public_registration && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/register\">\n {t('login.link.register')}\n </Link>\n }\n text={t('login.footer.noAccount')}\n />\n )}\n {hasMultipleLoginMethods && (\n <AuthFooterLink\n link={\n <Link search={extractOAuthParams(search)} to=\"/login\">\n {t('login.password.backToMethods')}\n </Link>\n }\n />\n )}\n </AuthFooter>\n )}\n </AuthLayout>\n );\n}\n"],"mappings":"wCAqBsF,IAAAC,EAA7ED"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{G as e,J as t,Z as n,_ as r,b as i,c as a,i as o,q as s,v as c,x as l}from"./createLucideIcon-BPpAIXR_.js";import{F as u,G as d,P as f,S as ee,U as p,Vt as m,W as h,Zt as te,bn as g,bt as ne,k as _,q as re,xt as v}from"./config-DTQCUlfZ.js";import{A as ie,C as ae,D as oe,T as se,b as ce,c as le,k as y,m as b,x as ue,y as de}from"./shell-header-bar-BPgCjFPv.js";import{t as x}from"./useAriaLabelledBy-9o83pv9X.js";import{t as S}from"./field-CEs4ugVJ.js";import{t as C}from"./separator-B0Bvh1he.js";var w=n(t(),1),T=w.createContext(void 0);function fe(){let e=w.useContext(T);if(e===void 0)throw Error(c(98));return e}function E(e,t,n){return{...e,value:t,index:n,filled:t!==``}}var D={value:()=>null,length:()=>null,...y},O={value:()=>null,index:()=>null,...y},k={numeric:{slotPattern:`\\d{1}`,getRootPattern:e=>`\\d{${e}}`,regexp:/[^\d]/g,inputMode:`numeric`},alpha:{slotPattern:`[a-zA-Z]{1}`,getRootPattern:e=>`[a-zA-Z]{${e}}`,regexp:/[^a-zA-Z]/g,inputMode:`text`},alphanumeric:{slotPattern:`[a-zA-Z0-9]{1}`,getRootPattern:e=>`[a-zA-Z0-9]{${e}}`,regexp:/[^a-zA-Z0-9]/g,inputMode:`text`}};function A(e){return e===`none`?null:k[e]}function j(e){return(e??``).replace(/\s/g,``)}function pe(e,t){return t?e.replace(t.regexp,``):e}function M(e,t,n,r){let i=j(e),a=A(n),o=pe(i,a),s=i.length>o.length;if(r){let e=r(o);s||=o.length>e.length,o=pe(e,a),s||=e.length>o.length}let c=t<0?0:t,l=Array.from(o);return[l.slice(0,c).join(``),s||l.length>c]}function N(e,t,n,r){return M(e,t,n,r)[0]}function me(e,t,n,r,i,a){let o=N(n,r,i,a);return N(`${e.slice(0,t)}${o}${e.slice(t+o.length)}`,r,i,a)}function P(e,t){return t<0||t>=e.length?e:`${e.slice(0,t)}${e.slice(t+1)}`}var F=s(),I=w.forwardRef(function(e,t){let{"aria-describedby":n,"aria-labelledby":o,id:s,autoComplete:c=`one-time-code`,defaultValue:d,value:ee,onValueChange:p,onValueComplete:m,form:h,length:g,autoSubmit:re=!1,mask:le=!1,inputMode:y,validationType:S=`numeric`,normalizeValue:C,disabled:fe=!1,readOnly:E=!1,required:O=!1,name:k,onValueInvalid:j,render:pe,className:me,style:P,...I}=e,{setDirty:R,validityData:z,disabled:B,setFilled:V,invalid:H,name:he,state:ge,validation:U,validationMode:_e,setFocused:ve,setTouched:ye}=oe(),{clearErrors:be}=ce(),{getDescriptionProps:xe,labelId:Se}=ae(),W=B||fe,G=he??k,[Ce,we]=ie({controlled:ee,default:d,name:`OTPField`,state:`value`}),K=w.useRef(null),q=w.useRef([]),Te=w.useRef(null),J=w.useRef(null),Ee=w.useMemo(()=>({get current(){return q.current[0]??null}}),[]),Y=ue({id:s}),De=x(o,Se,Ee,!0,Y),Oe=o==null?De:void 0,ke=L(n,xe({})[`aria-describedby`]),Ae=A(S),je=Ae?.slotPattern,Me=Ae?.getRootPattern(g),Ne=y??Ae?.inputMode,Pe=Number.isInteger(g)&&g>0,X=N(Ce,g,S,C),Z=_(X),Q=X!==``,[Fe,Ie]=w.useState(0),[Le,Re]=w.useState(()=>Math.min(X.length,g-1)),[ze,Be]=w.useState(!1),Ve=ze?Math.min(Le,Math.max(g-1,0)):Math.min(X.length,g-1);i(()=>{V(Q)},[Q,V]),se(Ee,Y,X,void 0,!W,k);let $=l(e=>{let t=Math.min(Math.max(e,0),Math.max(q.current.length-1,0)),n=q.current[t];n?.focus(),n?.select()}),He=l((e,t)=>{Te.current={index:e,value:t}});function Ue(){let e=U.inputRef.current?.form??q.current[0]?.form??null;if(h){let t=r(K.current).getElementById(h);t?.tagName===`FORM`&&(e=t)}e&&typeof e.requestSubmit==`function`&&e.requestSubmit()}function We(e,t){m?.(e,t),re&&Ue()}de(X,()=>{be(G),R(X!==z.initialValue),U.change(X);let e=Te.current;e!=null&&(Te.current=null,e.value===X&&$(e.index));let t=J.current;t!=null&&(J.current=null,t.value===X&&We(X,t.eventDetails))});let Ge=l((e,t)=>{let n=N(e,g,S,C),r=(t.reason===`input-change`||t.reason===`input-paste`)&&n.length===g&&(Z.current.length!==g||t.reason===`input-paste`)?u(t.reason,t.event):null;return n===Z.current?(r!=null&&We(n,r),null):(p?.(n,t),t.isCanceled?null:(we(n),r==null?n.length!==g&&(J.current=null):J.current={value:n,eventDetails:r},n))}),Ke=l((e,t)=>{j?.(e,t)}),qe=l((e,t)=>{if(e>Z.current.length){$(Math.min(Z.current.length,g-1));return}Re(e),Be(!0),ve(!0),t.currentTarget.select()}),Je=l(e=>{te(K.current,e.relatedTarget)||(ye(!0),Be(!1),ve(!1),_e===`onBlur`&&U.commit(Z.current))}),Ye=w.useCallback(e=>{if(Y!=null)return e===0?Y:`${Y}-${e+1}`},[Y]),Xe=w.useMemo(()=>({...ge,complete:X.length===g,disabled:W,filled:Q,focused:ze,length:g,readOnly:E,required:O,value:X}),[W,ge,Q,ze,g,E,O,X]),Ze=w.useMemo(()=>({autoComplete:c,activeIndex:Ve,disabled:W,form:h,focusInput:$,queueFocusInput:He,getInputId:Ye,handleInputBlur:Je,handleInputFocus:qe,inputMode:Ne,inputAriaLabelledBy:Oe,invalid:H,length:g,mask:le,pattern:je,reportValueInvalid:Ke,readOnly:E,required:O,normalizeValue:C,setValue:Ge,state:Xe,validationType:S,value:X}),[Ve,c,W,$,h,Ye,Je,qe,Ne,Oe,H,g,le,je,He,E,Ke,O,C,Ge,Xe,S,X]),Qe=a(`div`,e,{ref:[t,K],state:Xe,props:[{role:`group`,"aria-describedby":ke,"aria-labelledby":De},I],stateAttributesMapping:D});return(0,F.jsx)(b,{elementsRef:q,onMapChange:e=>{Ie(e.size)},children:(0,F.jsxs)(T.Provider,{value:Ze,children:[Qe,Pe&&(0,F.jsx)(`input`,{...U.getValidationProps(W,{onFocus(){$(0)},onChange(e){if(e.nativeEvent.defaultPrevented||W||E)return;let t=e.currentTarget.value,[n,r]=M(t,g,S,C);r&&Ke(t,u(`input-change`,e.nativeEvent));let i=Ge(n,f(`input-change`,e.nativeEvent));i!=null&&i!==``&&He(i.length-1,i)}}),ref:U.inputRef,type:`text`,id:Y&&G==null?`${Y}-hidden-input`:void 0,form:h,name:G,value:X,autoComplete:c,inputMode:Ne,minLength:g,maxLength:g,pattern:Me,disabled:W,readOnly:E,required:O,"aria-hidden":!0,tabIndex:-1,style:G?v:ne})]})})});function L(...e){let t=e.flatMap(e=>e?.split(/\s+/).filter(Boolean)??[]);return t.length>0?Array.from(new Set(t)).join(` `):void 0}var R=g(w.forwardRef(function(e,t){let{"aria-label":n,"aria-labelledby":r,render:i,className:o,style:s,...c}=e,{activeIndex:l,autoComplete:te,disabled:g,form:ne,focusInput:_,queueFocusInput:v,getInputId:ie,handleInputBlur:ae,handleInputFocus:oe,inputMode:se,inputAriaLabelledBy:ce,invalid:y,length:b,mask:ue,pattern:de,reportValueInvalid:x,readOnly:S,required:C,normalizeValue:T,setValue:D,state:k,validationType:A,value:j}=fe(),{ref:pe,index:N}=le({guess:!0}),F=w.useRef(null),I=ee(),L=j[N]??``,R=E(k,L,N),z=n,B=r??ce,V=N===0?void 0:z,H={id:ie(N),value:L,type:ue?`password`:`text`,inputMode:se,autoComplete:N===0?te:`off`,autoCorrect:`off`,spellCheck:`false`,enterKeyHint:N===b-1?`done`:`next`,maxLength:N===0?b:void 0,tabIndex:l===N?0:-1,disabled:g,form:ne,pattern:de,readOnly:S,required:C,"aria-labelledby":V==null?B:void 0,"aria-invalid":!g&&y?!0:void 0,"aria-label":V,onMouseDown(e){e.defaultPrevented||g||(e.preventDefault(),_(N))},onFocus(e){e.defaultPrevented||g||oe(N,e)},onBlur(e){e.defaultPrevented||ae(e)},onChange(e){if(e.defaultPrevented||g||S)return;let t=e.currentTarget.value,[n,r]=M(t,b,A,T);if(r&&x(t,u(p,e.nativeEvent)),n===``){t===``?D(P(j,N),f(h,e.nativeEvent)):L!==``&&(e.currentTarget.value=L,e.currentTarget.select());return}let i=me(j,N,n,b,A,T),a=D(i,f(p,e.nativeEvent));if(a!=null){let e=Math.min(N+n.length,b-1);v(e,a)}},onKeyDown(e){if(e.defaultPrevented||g)return;let t=Math.max(b-1,0),n=Math.min(j.length,t),r=(e.ctrlKey||e.metaKey)&&!e.altKey,i=I===`rtl`,a=i?`ArrowRight`:`ArrowLeft`,o=i?`ArrowLeft`:`ArrowRight`;if(e.key===a){m(e),_(r?0:Math.max(0,N-1));return}if(e.key===o){m(e),_(r?n:Math.min(t,N+1));return}if(e.key===`Home`||e.key===`ArrowUp`){m(e),_(0);return}if(e.key===`End`||e.key===`ArrowDown`){m(e),_(n);return}if(S)return;function s(t,n){let r=D(t,f(re,e.nativeEvent));r!=null&&v(n,r)}if(e.key===`Backspace`&&r){m(e),s(``,0);return}if(e.key===`Delete`){m(e),s(P(j,N),N);return}let c=e.currentTarget.value,l=e.currentTarget.selectionStart===0&&e.currentTarget.selectionEnd===c.length;if(e.key.length===1&&l&&L===e.key){m(e),N<b-1&&_(N+1);return}if(e.key===`Backspace`){m(e);let t=Math.max(0,N-1);s(P(j,L===``?t:N),t)}},onPaste(e){if(e.defaultPrevented||g||S)return;let t=``;try{t=e.clipboardData?.getData(`text/plain`)??``}catch{return}e.preventDefault();let[n,r]=M(t,b,A,T);if(r&&x(t,u(d,e.nativeEvent)),n===``)return;let i=D(me(j,N,n,b,A,T),f(d,e.nativeEvent));if(i!=null){let e=Math.min(N+n.length,b-1);v(e,i)}}};return a(`input`,e,{ref:[t,pe,F],state:R,props:[H,c],stateAttributesMapping:O})}),`tr-otp-field-digit`,{tracksFocusModality:!0});function z({appearance:t=`solid`,className:n,uiSize:r=`md`,...i}){return(0,F.jsx)(I,{...i,className:e(`tr-otp-field`,n),"data-appearance":t,"data-ui-size":r})}function B({className:t,orientation:n=`vertical`,...r}){return(0,F.jsx)(C,{...r,className:e(`tr-otp-field-separator`,t),orientation:n})}var V={Root:z,Input:R,Separator:B},H=(0,w.forwardRef)(function({length:e=6,error:t,disabled:n=!1,onComplete:r,onChange:i,value:a,autoFocus:s=!1,className:c=``},l){let{t:u}=o(),d=(0,w.useId)(),f=(0,w.useRef)(null),[ee,p]=(0,w.useState)(a??``);(0,w.useEffect)(()=>{a!==void 0&&p(a)},[a]),(0,w.useImperativeHandle)(l,()=>({focus:()=>{(f.current?.querySelector(`input`))?.focus()},clear:()=>{p(``),(f.current?.querySelector(`input`))?.focus()}}),[]);let m=(0,w.useCallback)(e=>{p(e),i?.(e)},[i]),h=(0,w.useCallback)(e=>{r?.(e)},[r]);return(0,F.jsxs)(S.Root,{className:c,ref:f,children:[(0,F.jsx)(S.Label,{className:`sr-only`,htmlFor:d,children:u(`common.otp.label`)}),(0,F.jsx)(V.Root,{disabled:n,id:d,length:e,onValueChange:m,onValueComplete:h,uiSize:`md`,validationType:`numeric`,value:ee,children:Array.from({length:e},(t,n)=>(0,F.jsx)(V.Input,{"aria-label":n===0?void 0:u(`common.otp.digit`,{label:u(`common.otp.label`),position:n+1,total:e}),autoFocus:s&&n===0},n))}),t&&(0,F.jsx)(`p`,{className:`mt-tinyrack-sm text-center text-tinyrack-danger-foreground text-tinyrack-sm`,"data-testid":`pin-input-error`,children:t.message})]})});export{H as t};
|
|
2
|
+
//# sourceMappingURL=pin-input-DdhzDNy9.js.map
|