From baa8577c45f109e376ad0b3d60ee29f07e86fe9e Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 9 Sep 2024 10:08:52 +0800 Subject: [PATCH] refactor(experience): experience api migration (#6407) * refactor(experience): migrate the password register and sign-in migrate the password register and sign-in flow * fix(experience): update some namings update some namings * refactor(experience): refactor the verification code flow (migration-2) (#6408) * refactor(experience): refactor the verificaiton code flow refactor the verification code flow * refactor(experience): migrate the social and sso flow (migration-3) (#6406) * refactor(experience): migrate the social and sso flow migrate the social and sso flow * refactor(experience): migrate profile fulfillment flow (migration-4) (#6414) * refactor(experience): migrate profile fulfillment flow migrate the profile fulfillment flow * refactor(experience): remove unused hook remove unused hook * fix(experience): fix password policy checker fix password policy checker error display * fix(experience): fix the api name fix the api name * refactor(experience): migrate mfa flow (migration-5) (#6417) * refactor(experience): migrate mfa binding flow migrate mfa binding flow * test(experience): update unit tests (migration-6) (#6420) * test(experience): update unit tests update unit tests * chore(experience): remove legacy APIs remove legacy APIs * refactor(experience): revert api prefix revert api prefix * fix(experience): update the sso connectors endpoint update the sso connectors endpoint * chore: add changeset add changeset * fix(experience): comments fix comments fix * refactor(experience): refactor the code verificatin api refactor the code verification api * refactor(experience): code refactor refactor some implementation logic * feat(experience, core): add experience legacy package (#6527) add experience legacy package --- .changeset/fresh-shrimps-rhyme.md | 12 + package.json | 2 +- packages/core/package.json | 1 + packages/core/src/middleware/koa-spa-proxy.ts | 2 +- packages/experience-legacy/.eslintrc.cjs | 22 + packages/experience-legacy/CHANGELOG.md | 727 ++++++++++++++++++ packages/experience-legacy/README.md | 3 + packages/experience-legacy/index.html | 20 + packages/experience-legacy/jest.config.ts | 35 + packages/experience-legacy/package.json | 99 +++ packages/experience-legacy/src/App.tsx | 168 ++++ .../src/Layout/AppLayout/CustomContent.tsx | 28 + .../src/Layout/AppLayout/index.module.scss | 66 ++ .../src/Layout/AppLayout/index.tsx | 29 + .../FirstScreenLayout/index.module.scss | 25 + .../src/Layout/FirstScreenLayout/index.tsx | 28 + .../FocusedAuthPageLayout/index.module.scss | 33 + .../Layout/FocusedAuthPageLayout/index.tsx | 61 ++ .../LandingPageLayout/index.module.scss | 36 + .../src/Layout/LandingPageLayout/index.tsx | 54 ++ .../SecondaryPageLayout/index.module.scss | 53 ++ .../src/Layout/SecondaryPageLayout/index.tsx | 67 ++ .../Layout/SectionLayout/index.module.scss | 11 + .../src/Layout/SectionLayout/index.tsx | 30 + .../Layout/StaticPageLayout/index.module.scss | 7 + .../src/Layout/StaticPageLayout/index.tsx | 11 + .../src/Providers/AppBoundary/AppMeta.tsx | 56 ++ .../Providers/AppBoundary/index.module.scss | 37 + .../src/Providers/AppBoundary/index.tsx | 30 + .../Providers/AppBoundary/use-color-theme.ts | 62 ++ .../Providers/ConfirmModalProvider/index.tsx | 161 ++++ .../ConfirmModalProvider/indext.test.tsx | 203 +++++ .../IframeModal/index.module.scss | 71 ++ .../IframeModalProvider/IframeModal/index.tsx | 71 ++ .../Providers/IframeModalProvider/index.tsx | 55 ++ .../Providers/LoadingLayerProvider/index.tsx | 18 + .../PageContextProvider/PageContext.tsx | 38 + .../Providers/PageContextProvider/index.tsx | 66 ++ .../src/Providers/SettingsProvider/index.tsx | 22 + .../Providers/SettingsProvider/use-preview.ts | 83 ++ .../use-sign-in-experience.ts | 24 + .../Providers/SettingsProvider/use-theme.ts | 30 + .../SingleSignOnFormModeContext.tsx | 18 + .../index.tsx | 23 + .../src/Providers/ToastProvider/index.tsx | 27 + .../UserInteractionContext.tsx | 58 ++ .../UserInteractionContextProvider/index.tsx | 114 +++ .../SettingsProvider.tsx | 29 + .../__mocks__/RenderWithPageContext/index.tsx | 24 + .../experience-legacy/src/__mocks__/logto.tsx | 221 ++++++ .../src/__mocks__/social-connectors.tsx | 124 +++ packages/experience-legacy/src/apis/api.ts | 12 + .../experience-legacy/src/apis/consent.ts | 21 + .../src/apis/interaction.ts | 0 .../experience-legacy/src/apis/settings.ts | 63 ++ .../src/apis/single-sign-on.ts | 0 packages/experience-legacy/src/apis/utils.ts | 26 + .../src/assets/apple-touch-icon.png | Bin 0 -> 83873 bytes .../experience-legacy/src/assets/favicon.png | Bin 0 -> 592 bytes .../src/assets/icons/arrow-down.svg | 3 + .../src/assets/icons/arrow-next.svg | 3 + .../src/assets/icons/arrow-prev.svg | 3 + .../src/assets/icons/check-mark.svg | 3 + .../src/assets/icons/checkbox-icon.svg | 5 + .../src/assets/icons/clear-icon.svg | 4 + .../src/assets/icons/close-icon.svg | 4 + .../src/assets/icons/connect-icon.svg | 6 + .../src/assets/icons/default-user-avatar.svg | 7 + .../src/assets/icons/dev-icon.svg | 5 + .../src/assets/icons/empty-state-dark.svg | 58 ++ .../src/assets/icons/empty-state.svg | 61 ++ .../src/assets/icons/expand-icon.svg | 4 + .../src/assets/icons/expandable-icon.svg | 4 + .../src/assets/icons/factor-backup-code.svg | 3 + .../src/assets/icons/factor-totp.svg | 3 + .../src/assets/icons/factor-webauthn.svg | 19 + .../src/assets/icons/info-icon.svg | 14 + .../src/assets/icons/loading-icon.svg | 15 + .../src/assets/icons/loading-ring.svg | 3 + .../src/assets/icons/lock.svg | 4 + .../src/assets/icons/logto-logo-dark.svg | 22 + .../src/assets/icons/logto-logo-light.svg | 21 + .../src/assets/icons/logto-logo-shadow.svg | 15 + .../src/assets/icons/more-social-icon.svg | 3 + .../src/assets/icons/nav-close.svg | 3 + .../src/assets/icons/organization-icon.svg | 4 + .../src/assets/icons/password-hide-icon.svg | 3 + .../src/assets/icons/password-show-icon.svg | 3 + .../src/assets/icons/search-icon.svg | 3 + .../src/assets/icons/switch-icon.svg | 3 + .../experience-legacy/src/assets/index.d.ts | 6 + .../BrandingHeader/index.module.scss | 56 ++ .../components/BrandingHeader/index.test.tsx | 17 + .../src/components/BrandingHeader/index.tsx | 57 ++ .../components/Button/IconButton.module.scss | 29 + .../src/components/Button/IconButton.tsx | 17 + .../Button/MfaFactorButton.module.scss | 39 + .../src/components/Button/MfaFactorButton.tsx | 73 ++ .../Button/RotatingRingIcon.module.scss | 14 + .../components/Button/RotatingRingIcon.tsx | 7 + .../Button/SocialLinkButton.module.scss | 49 ++ .../components/Button/SocialLinkButton.tsx | 73 ++ .../src/components/Button/index.module.scss | 113 +++ .../src/components/Button/index.test.tsx | 47 ++ .../src/components/Button/index.tsx | 72 ++ .../src/components/Checkbox/index.module.scss | 56 ++ .../src/components/Checkbox/index.tsx | 22 + .../src/components/ConfirmModal/AcModal.tsx | 85 ++ .../ConfirmModal/Acmodal.module.scss | 67 ++ .../ConfirmModal/MobileModal.module.scss | 76 ++ .../components/ConfirmModal/MobileModal.tsx | 57 ++ .../src/components/ConfirmModal/index.tsx | 4 + .../src/components/ConfirmModal/type.ts | 16 + .../src/components/Divider/index.module.scss | 24 + .../src/components/Divider/index.test.tsx | 10 + .../src/components/Divider/index.tsx | 29 + .../src/components/DynamicT/index.test.tsx | 26 + .../src/components/DynamicT/index.tsx | 31 + .../components/ErrorMessage/index.module.scss | 10 + .../src/components/ErrorMessage/index.tsx | 47 ++ .../src/components/GoogleOneTap/index.tsx | 43 ++ .../IdentifierRegisterForm/index.module.scss | 29 + .../IdentifierRegisterForm/index.test.tsx | 426 ++++++++++ .../IdentifierRegisterForm/index.tsx | 174 +++++ .../IdentifierRegisterForm/use-on-submit.ts | 62 ++ .../use-register-with-username.ts | 46 ++ .../IdentifierSignInForm/index.module.scss | 29 + .../IdentifierSignInForm/index.test.tsx | 293 +++++++ .../components/IdentifierSignInForm/index.tsx | 171 ++++ .../IdentifierSignInForm/use-on-submit.ts | 84 ++ .../NotchedBorder/index.module.scss | 131 ++++ .../InputField/NotchedBorder/index.tsx | 54 ++ .../InputFields/InputField/index.module.scss | 141 ++++ .../InputFields/InputField/index.test.tsx | 43 ++ .../InputFields/InputField/index.tsx | 147 ++++ .../PasswordInputField/index.test.tsx | 59 ++ .../InputFields/PasswordInputField/index.tsx | 41 + .../AnimatedPrefix/index.module.scss | 7 + .../SmartInputField/AnimatedPrefix/index.tsx | 56 ++ .../CountryCodeDropdown/index.module.scss | 139 ++++ .../CountryCodeDropdown/index.test.tsx | 148 ++++ .../CountryCodeDropdown/index.tsx | 281 +++++++ .../CountryCodeSelector/index.module.scss | 49 ++ .../CountryCodeSelector/index.tsx | 65 ++ .../SmartInputField/index.test.tsx | 279 +++++++ .../InputFields/SmartInputField/index.tsx | 101 +++ .../SmartInputField/use-smart-input-field.ts | 96 +++ .../InputFields/SmartInputField/utils.test.ts | 54 ++ .../InputFields/SmartInputField/utils.ts | 96 +++ .../src/components/InputFields/index.tsx | 3 + .../components/LoadingLayer/LoadingIcon.tsx | 15 + .../components/LoadingLayer/index.module.scss | 28 + .../src/components/LoadingLayer/index.tsx | 16 + .../components/LoadingMask/index.module.scss | 8 + .../src/components/LoadingMask/index.tsx | 13 + .../LogtoSignature/index.module.scss | 45 ++ .../src/components/LogtoSignature/index.tsx | 41 + .../src/components/NavBar/index.module.scss | 70 ++ .../src/components/NavBar/index.tsx | 70 ++ .../AppNotification/index.module.scss | 29 + .../Notification/AppNotification/index.tsx | 31 + .../InlineNotification/index.module.scss | 10 + .../Notification/InlineNotification/index.tsx | 21 + .../src/components/Notification/index.tsx | 2 + .../src/components/PageMeta/index.tsx | 17 + .../PasswordSignInForm/index.module.scss | 44 ++ .../PasswordSignInForm/index.test.tsx | 309 ++++++++ .../components/PasswordSignInForm/index.tsx | 188 +++++ .../SingleSignOnForm/index.module.scss | 23 + .../src/components/SingleSignOnForm/index.tsx | 121 +++ .../components/SwitchMfaFactorsLink/index.tsx | 35 + .../components/TermsLinks/index.module.scss | 24 + .../src/components/TermsLinks/index.tsx | 50 ++ .../src/components/TextLink/index.module.scss | 47 ++ .../src/components/TextLink/index.test.tsx | 15 + .../src/components/TextLink/index.tsx | 76 ++ .../src/components/Toast/index.module.scss | 53 ++ .../src/components/Toast/index.test.tsx | 35 + .../src/components/Toast/index.tsx | 47 ++ .../VerificationCode/index.module.scss | 46 ++ .../VerificationCode/index.test.tsx | 215 ++++++ .../src/components/VerificationCode/index.tsx | 221 ++++++ .../experience-legacy/src/constants/env.ts | 9 + .../DevelopmentTenantNotification/index.tsx | 99 +++ .../containers/ForgotPasswordLink/index.tsx | 35 + .../MfaFactorList/index.module.scss | 6 + .../src/containers/MfaFactorList/index.tsx | 55 ++ .../SetPassword/HiddenIdentifierInput.tsx | 25 + .../src/containers/SetPassword/Lite.test.tsx | 57 ++ .../src/containers/SetPassword/Lite.tsx | 85 ++ .../SetPassword/SetPassword.test.tsx | 104 +++ .../containers/SetPassword/SetPassword.tsx | 134 ++++ .../containers/SetPassword/TogglePassword.tsx | 41 + .../containers/SetPassword/index.module.scss | 33 + .../src/containers/SetPassword/index.tsx | 26 + .../SocialLanding/index.module.scss | 22 + .../src/containers/SocialLanding/index.tsx | 32 + .../SocialLinkAccount/index.module.scss | 33 + .../SocialLinkAccount/index.test.tsx | 122 +++ .../containers/SocialLinkAccount/index.tsx | 83 ++ .../use-social-link-related-user.ts | 34 + .../SocialSignInList/index.module.scss | 13 + .../SocialSignInList/index.test.tsx | 16 + .../src/containers/SocialSignInList/index.tsx | 52 ++ .../containers/SocialSignInList/use-social.ts | 103 +++ .../TermsAndPrivacyCheckbox/index.module.scss | 30 + .../TermsAndPrivacyCheckbox/index.tsx | 60 ++ .../TermsAndPrivacyCheckbox/intext.test.tsx | 21 + .../index.tsx | 34 + .../containers/TermsAndPrivacyLinks/index.tsx | 46 ++ .../TotpCodeVerification/index.module.scss | 9 + .../containers/TotpCodeVerification/index.tsx | 83 ++ .../use-totp-code-verification.ts | 38 + .../VerificationCode/PasswordSignInLink.tsx | 21 + .../VerificationCode/index.module.scss | 34 + .../VerificationCode/index.test.tsx | 366 +++++++++ .../src/containers/VerificationCode/index.tsx | 135 ++++ .../use-continue-flow-code-verification.ts | 100 +++ ...-forgot-password-flow-code-verification.ts | 65 ++ ...general-verification-code-error-handler.ts | 30 + .../use-identifier-error-alert.ts | 48 ++ .../use-link-social-confirm-modal.ts | 44 ++ .../use-register-flow-code-verification.ts | 134 ++++ .../use-resend-verification-code.ts | 59 ++ .../use-sign-in-flow-code-verification.ts | 135 ++++ .../src/containers/VerificationCode/utils.ts | 15 + packages/experience-legacy/src/favicon.ico | Bin 0 -> 15406 bytes .../experience-legacy/src/hooks/use-api.ts | 37 + .../src/hooks/use-check-single-sign-on.ts | 105 +++ .../src/hooks/use-confirm-modal.ts | 50 ++ .../src/hooks/use-connectors.ts | 63 ++ .../src/hooks/use-debounce.ts | 44 ++ .../src/hooks/use-error-handler.ts | 60 ++ .../src/hooks/use-global-redirect-to.ts | 82 ++ .../src/hooks/use-identifier-params.test.ts | 55 ++ .../src/hooks/use-identifier-params.ts | 39 + .../src/hooks/use-login-hint.ts | 10 + .../src/hooks/use-mfa-error-handler.ts | 150 ++++ .../src/hooks/use-mfa-factors-state.ts | 13 + .../src/hooks/use-native-message-listener.ts | 49 ++ .../src/hooks/use-password-action.ts | 0 .../src/hooks/use-password-error-message.ts | 75 ++ .../src/hooks/use-password-sign-in.ts | 69 ++ .../src/hooks/use-platform.ts | 11 + .../hooks/use-pre-sign-in-error-handler.ts | 26 + .../src/hooks/use-prefilled-identifier.ts | 89 +++ .../use-required-profile-error-handler.ts | 74 ++ .../src/hooks/use-send-mfa-payload.ts | 60 ++ .../src/hooks/use-send-verification-code.ts | 68 ++ .../src/hooks/use-session-storages.test.ts | 39 + .../src/hooks/use-session-storages.ts | 75 ++ .../experience-legacy/src/hooks/use-sie.ts | 87 +++ .../src/hooks/use-single-sign-on-watch.ts | 139 ++++ .../src/hooks/use-single-sign-on.ts | 76 ++ .../src/hooks/use-skip-mfa.ts | 30 + .../src/hooks/use-social-link-account.ts | 32 + .../src/hooks/use-social-register.ts | 35 + .../src/hooks/use-start-totp-binding.ts | 45 ++ .../hooks/use-start-webauthn-processing.ts | 50 ++ .../experience-legacy/src/hooks/use-terms.ts | 60 ++ .../src/hooks/use-text-handler.ts | 32 + .../experience-legacy/src/hooks/use-toast.ts | 11 + .../experience-legacy/src/hooks/use-toggle.ts | 13 + .../src/hooks/use-webauthn-operation.ts | 84 ++ packages/experience-legacy/src/i18n/init.ts | 39 + packages/experience-legacy/src/i18n/utils.ts | 81 ++ .../experience-legacy/src/include.d/dom.d.ts | 79 ++ .../src/include.d/global.d.ts | 23 + .../src/include.d/i18next.d.ts | 9 + .../src/include.d/react-router-dom.d.ts | 15 + .../src/include.d/vite-env.d.ts | 2 + packages/experience-legacy/src/index.tsx | 11 + packages/experience-legacy/src/jest.setup.ts | 24 + .../src/pages/Callback/index.module.scss | 6 + .../src/pages/Callback/index.tsx | 41 + .../Callback/use-social-callback-handler.ts | 50 ++ .../OrganizationItem/index.module.scss | 32 + .../OrganizationItem/index.tsx | 54 ++ .../index.module.scss | 67 ++ .../OrganizationSelectorModal/index.tsx | 98 +++ .../OrganizationSelector/index.module.scss | 43 ++ .../Consent/OrganizationSelector/index.tsx | 98 +++ .../Consent/ScopeGroup/index.module.scss | 53 ++ .../src/pages/Consent/ScopeGroup/index.tsx | 54 ++ .../Consent/ScopesListCard/index.module.scss | 72 ++ .../pages/Consent/ScopesListCard/index.tsx | 69 ++ .../Consent/UserProfile/index.module.scss | 26 + .../src/pages/Consent/UserProfile/index.tsx | 35 + .../src/pages/Consent/index.module.scss | 46 ++ .../src/pages/Consent/index.tsx | 155 ++++ .../src/pages/Consent/util.test.ts | 15 + .../src/pages/Consent/util.ts | 17 + .../IdentifierProfileForm/index.module.scss | 19 + .../Continue/IdentifierProfileForm/index.tsx | 124 +++ .../SocialIdentityNotification.tsx | 57 ++ .../SetEmailOrPhone/index.module.scss | 6 + .../Continue/SetEmailOrPhone/index.test.tsx | 101 +++ .../pages/Continue/SetEmailOrPhone/index.tsx | 91 +++ .../pages/Continue/SetPassword/index.test.tsx | 121 +++ .../src/pages/Continue/SetPassword/index.tsx | 77 ++ .../pages/Continue/SetUsername/index.test.tsx | 58 ++ .../src/pages/Continue/SetUsername/index.tsx | 49 ++ .../Continue/SetUsername/use-set-username.ts | 53 ++ .../src/pages/Continue/index.tsx | 36 + .../src/pages/DirectSignIn/index.test.tsx | 123 +++ .../src/pages/DirectSignIn/index.tsx | 46 ++ .../src/pages/ErrorPage/index.module.scss | 42 + .../src/pages/ErrorPage/index.test.tsx | 16 + .../src/pages/ErrorPage/index.tsx | 42 + .../ForgotPasswordForm/index.module.scss | 19 + .../ForgotPasswordForm/index.test.tsx | 100 +++ .../ForgotPasswordForm/index.tsx | 123 +++ .../src/pages/ForgotPassword/index.test.tsx | 133 ++++ .../src/pages/ForgotPassword/index.tsx | 38 + .../src/pages/IdentifierRegister/index.tsx | 56 ++ .../use-identifier-sign-up-methods.ts | 35 + .../src/pages/IdentifierSignIn/index.tsx | 70 ++ .../use-identifier-sign-in-methods.ts | 35 + .../BackupCodeBinding/index.module.scss | 36 + .../MfaBinding/BackupCodeBinding/index.tsx | 84 ++ .../SecretSection/index.module.scss | 37 + .../TotpBinding/SecretSection/index.tsx | 59 ++ .../TotpBinding/VerificationSection.tsx | 24 + .../MfaBinding/TotpBinding/index.module.scss | 12 + .../pages/MfaBinding/TotpBinding/index.tsx | 52 ++ .../WebAuthnBinding/index.module.scss | 5 + .../MfaBinding/WebAuthnBinding/index.tsx | 59 ++ .../src/pages/MfaBinding/index.tsx | 30 + .../BackupCodeVerification/index.module.scss | 9 + .../BackupCodeVerification/index.tsx | 72 ++ .../TotpVerification/index.module.scss | 5 + .../TotpVerification/index.tsx | 35 + .../WebAuthnVerification/index.module.scss | 5 + .../WebAuthnVerification/index.tsx | 58 ++ .../src/pages/MfaVerification/index.tsx | 22 + .../src/pages/Register/index.module.scss | 34 + .../src/pages/Register/index.test.tsx | 103 +++ .../src/pages/Register/index.tsx | 131 ++++ .../src/pages/RegisterPassword/index.test.tsx | 154 ++++ .../src/pages/RegisterPassword/index.tsx | 87 +++ .../src/pages/ResetPassword/index.test.tsx | 79 ++ .../src/pages/ResetPassword/index.tsx | 82 ++ .../src/pages/ResetPasswordLanding/index.tsx | 65 ++ .../use-reset-password-methods.ts | 41 + .../src/pages/SignIn/Main.tsx | 56 ++ .../src/pages/SignIn/index.module.scss | 38 + .../src/pages/SignIn/index.test.tsx | 142 ++++ .../src/pages/SignIn/index.tsx | 123 +++ .../PasswordForm/VerificationCodeLink.tsx | 43 ++ .../PasswordForm/index.test.tsx | 103 +++ .../SignInPassword/PasswordForm/index.tsx | 128 +++ .../pages/SignInPassword/index.module.scss | 42 + .../src/pages/SignInPassword/index.test.tsx | 100 +++ .../src/pages/SignInPassword/index.tsx | 55 ++ .../SingleSignOnConnectors/index.module.scss | 13 + .../pages/SingleSignOnConnectors/index.tsx | 69 ++ .../src/pages/SingleSignOnEmail/index.tsx | 15 + .../src/pages/SingleSignOnLanding/index.tsx | 39 + .../src/pages/SocialLanding/index.module.scss | 5 + .../src/pages/SocialLanding/index.test.tsx | 57 ++ .../src/pages/SocialLanding/index.tsx | 47 ++ .../use-social-landing-handler.ts | 40 + .../pages/SocialLinkAccount/index.test.tsx | 96 +++ .../src/pages/SocialLinkAccount/index.tsx | 57 ++ .../SocialSignInWebCallback/SingleSignOn.tsx | 25 + .../SocialSignInWebCallback/SocialSignIn.tsx | 18 + .../SocialSignInWebCallback/index.test.tsx | 158 ++++ .../pages/SocialSignInWebCallback/index.tsx | 27 + .../use-single-sign-on-listener.ts | 156 ++++ .../use-social-sign-in-listener.ts | 182 +++++ .../src/pages/Springboard/index.tsx | 32 + .../src/pages/VerificationCode/index.test.tsx | 52 ++ .../src/pages/VerificationCode/index.tsx | 69 ++ .../experience-legacy/src/scss/_colors.scss | 229 ++++++ .../experience-legacy/src/scss/_fonts.scss | 29 + .../src/scss/_underscore.scss | 60 ++ .../src/scss/modal.module.scss | 10 + .../src/scss/normalized.scss | 44 ++ packages/experience-legacy/src/types/guard.ts | 132 ++++ packages/experience-legacy/src/types/index.ts | 47 ++ packages/experience-legacy/src/utils/a11y.ts | 26 + .../experience-legacy/src/utils/consts.ts | 7 + .../experience-legacy/src/utils/cookies.ts | 8 + .../src/utils/country-code.test.ts | 92 +++ .../src/utils/country-code.ts | 116 +++ packages/experience-legacy/src/utils/form.ts | 105 +++ .../src/utils/format.test.ts | 13 + .../experience-legacy/src/utils/format.ts | 9 + .../experience-legacy/src/utils/index.test.ts | 30 + packages/experience-legacy/src/utils/index.ts | 46 ++ packages/experience-legacy/src/utils/logo.ts | 42 + .../experience-legacy/src/utils/native-sdk.ts | 14 + .../src/utils/search-parameters.ts | 42 + .../src/utils/sign-in-experience.test.ts | 23 + .../src/utils/sign-in-experience.ts | 70 ++ .../src/utils/social-connectors.test.ts | 155 ++++ .../src/utils/social-connectors.ts | 223 ++++++ .../experience-legacy/src/utils/webauthn.ts | 10 + packages/experience-legacy/tsconfig.json | 15 + packages/experience-legacy/vite.config.ts | 57 ++ .../UserInteractionContext.tsx | 7 +- .../UserInteractionContextProvider/index.tsx | 26 +- .../experience/src/apis/experience/const.ts | 14 + .../experience/src/apis/experience/index.ts | 149 ++++ .../src/apis/experience/interaction.ts | 41 + .../experience/src/apis/experience/mfa.ts | 126 +++ .../experience/src/apis/experience/social.ts | 94 +++ packages/experience/src/apis/utils.ts | 52 +- .../IdentifierRegisterForm/index.test.tsx | 77 +- .../use-register-with-username.ts | 14 +- .../IdentifierSignInForm/index.test.tsx | 27 +- .../PasswordSignInForm/index.test.tsx | 31 +- .../components/PasswordSignInForm/index.tsx | 2 +- .../SocialLinkAccount/index.test.tsx | 44 +- .../containers/SocialLinkAccount/index.tsx | 10 +- .../use-social-link-related-user.ts | 2 +- .../containers/SocialSignInList/use-social.ts | 17 +- .../containers/TotpCodeVerification/index.tsx | 23 +- .../use-totp-code-verification.ts | 13 +- .../VerificationCode/index.test.tsx | 197 ++--- .../src/containers/VerificationCode/index.tsx | 27 +- .../use-continue-flow-code-verification.ts | 89 ++- ...-forgot-password-flow-code-verification.ts | 45 +- .../use-link-social-confirm-modal.ts | 22 +- .../use-register-flow-code-verification.ts | 80 +- .../use-resend-verification-code.ts | 47 +- .../use-sign-in-flow-code-verification.ts | 74 +- .../src/hooks/use-check-single-sign-on.ts | 10 +- .../experience/src/hooks/use-error-handler.ts | 2 +- .../src/hooks/use-mfa-error-handler.ts | 39 +- .../src/hooks/use-password-policy-checker.ts | 32 + .../hooks/use-password-rejection-handler.ts | 31 + .../src/hooks/use-password-sign-in.ts | 12 +- .../hooks/use-pre-sign-in-error-handler.ts | 4 +- .../use-required-profile-error-handler.ts | 25 +- .../src/hooks/use-send-mfa-payload.ts | 10 +- .../src/hooks/use-send-verification-code.ts | 36 +- .../src/hooks/use-session-storages.ts | 8 +- .../src/hooks/use-single-sign-on-watch.ts | 8 +- .../src/hooks/use-single-sign-on.ts | 28 +- packages/experience/src/hooks/use-skip-mfa.ts | 2 +- .../src/hooks/use-social-link-account.ts | 17 +- .../src/hooks/use-social-register.ts | 21 +- .../hooks/use-start-backup-code-binding.ts | 45 ++ .../src/hooks/use-start-totp-binding.ts | 26 +- .../hooks/use-start-webauthn-processing.ts | 38 +- .../src/hooks/use-webauthn-operation.ts | 33 +- .../Continue/SetEmailOrPhone/index.test.tsx | 24 +- .../pages/Continue/SetEmailOrPhone/index.tsx | 10 +- .../pages/Continue/SetPassword/index.test.tsx | 23 +- .../src/pages/Continue/SetPassword/index.tsx | 57 +- .../pages/Continue/SetUsername/index.test.tsx | 16 +- .../src/pages/Continue/SetUsername/index.tsx | 9 +- .../Continue/SetUsername/use-set-username.ts | 19 +- .../experience/src/pages/Continue/index.tsx | 19 +- .../ForgotPasswordForm/index.test.tsx | 21 +- .../MfaBinding/BackupCodeBinding/index.tsx | 8 +- .../TotpBinding/VerificationSection.tsx | 8 +- .../pages/MfaBinding/TotpBinding/index.tsx | 10 +- .../MfaBinding/WebAuthnBinding/index.tsx | 11 +- .../WebAuthnVerification/index.tsx | 11 +- .../src/pages/RegisterPassword/index.test.tsx | 8 +- .../src/pages/RegisterPassword/index.tsx | 46 +- .../src/pages/ResetPassword/index.test.tsx | 8 +- .../src/pages/ResetPassword/index.tsx | 63 +- .../PasswordForm/index.test.tsx | 23 +- .../SignInPassword/PasswordForm/index.tsx | 4 +- .../src/pages/SignInPassword/index.test.tsx | 2 - .../pages/SocialLinkAccount/index.test.tsx | 48 +- .../src/pages/SocialLinkAccount/index.tsx | 18 +- .../SocialSignInWebCallback/index.test.tsx | 80 +- .../use-single-sign-on-listener.ts | 40 +- .../use-social-sign-in-listener.ts | 115 ++- .../src/pages/VerificationCode/index.test.tsx | 3 +- .../src/pages/VerificationCode/index.tsx | 32 +- packages/experience/src/types/guard.test.ts | 31 + packages/experience/src/types/guard.ts | 36 +- packages/experience/src/types/index.ts | 3 + .../src/utils/sign-in-experience.ts | 7 +- pnpm-lock.yaml | 212 ++++- 480 files changed, 25117 insertions(+), 722 deletions(-) create mode 100644 .changeset/fresh-shrimps-rhyme.md create mode 100644 packages/experience-legacy/.eslintrc.cjs create mode 100644 packages/experience-legacy/CHANGELOG.md create mode 100644 packages/experience-legacy/README.md create mode 100644 packages/experience-legacy/index.html create mode 100644 packages/experience-legacy/jest.config.ts create mode 100644 packages/experience-legacy/package.json create mode 100644 packages/experience-legacy/src/App.tsx create mode 100644 packages/experience-legacy/src/Layout/AppLayout/CustomContent.tsx create mode 100644 packages/experience-legacy/src/Layout/AppLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/AppLayout/index.tsx create mode 100644 packages/experience-legacy/src/Layout/FirstScreenLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/FirstScreenLayout/index.tsx create mode 100644 packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.tsx create mode 100644 packages/experience-legacy/src/Layout/LandingPageLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/LandingPageLayout/index.tsx create mode 100644 packages/experience-legacy/src/Layout/SecondaryPageLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/SecondaryPageLayout/index.tsx create mode 100644 packages/experience-legacy/src/Layout/SectionLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/SectionLayout/index.tsx create mode 100644 packages/experience-legacy/src/Layout/StaticPageLayout/index.module.scss create mode 100644 packages/experience-legacy/src/Layout/StaticPageLayout/index.tsx create mode 100644 packages/experience-legacy/src/Providers/AppBoundary/AppMeta.tsx create mode 100644 packages/experience-legacy/src/Providers/AppBoundary/index.module.scss create mode 100644 packages/experience-legacy/src/Providers/AppBoundary/index.tsx create mode 100644 packages/experience-legacy/src/Providers/AppBoundary/use-color-theme.ts create mode 100644 packages/experience-legacy/src/Providers/ConfirmModalProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/ConfirmModalProvider/indext.test.tsx create mode 100644 packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.module.scss create mode 100644 packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.tsx create mode 100644 packages/experience-legacy/src/Providers/IframeModalProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/LoadingLayerProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/PageContextProvider/PageContext.tsx create mode 100644 packages/experience-legacy/src/Providers/PageContextProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/SettingsProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/SettingsProvider/use-preview.ts create mode 100644 packages/experience-legacy/src/Providers/SettingsProvider/use-sign-in-experience.ts create mode 100644 packages/experience-legacy/src/Providers/SettingsProvider/use-theme.ts create mode 100644 packages/experience-legacy/src/Providers/SingleSignOnFormModeContextProvider/SingleSignOnFormModeContext.tsx create mode 100644 packages/experience-legacy/src/Providers/SingleSignOnFormModeContextProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/ToastProvider/index.tsx create mode 100644 packages/experience-legacy/src/Providers/UserInteractionContextProvider/UserInteractionContext.tsx create mode 100644 packages/experience-legacy/src/Providers/UserInteractionContextProvider/index.tsx create mode 100644 packages/experience-legacy/src/__mocks__/RenderWithPageContext/SettingsProvider.tsx create mode 100644 packages/experience-legacy/src/__mocks__/RenderWithPageContext/index.tsx create mode 100644 packages/experience-legacy/src/__mocks__/logto.tsx create mode 100644 packages/experience-legacy/src/__mocks__/social-connectors.tsx create mode 100644 packages/experience-legacy/src/apis/api.ts create mode 100644 packages/experience-legacy/src/apis/consent.ts rename packages/{experience => experience-legacy}/src/apis/interaction.ts (100%) create mode 100644 packages/experience-legacy/src/apis/settings.ts rename packages/{experience => experience-legacy}/src/apis/single-sign-on.ts (100%) create mode 100644 packages/experience-legacy/src/apis/utils.ts create mode 100644 packages/experience-legacy/src/assets/apple-touch-icon.png create mode 100644 packages/experience-legacy/src/assets/favicon.png create mode 100644 packages/experience-legacy/src/assets/icons/arrow-down.svg create mode 100644 packages/experience-legacy/src/assets/icons/arrow-next.svg create mode 100644 packages/experience-legacy/src/assets/icons/arrow-prev.svg create mode 100644 packages/experience-legacy/src/assets/icons/check-mark.svg create mode 100644 packages/experience-legacy/src/assets/icons/checkbox-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/clear-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/close-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/connect-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/default-user-avatar.svg create mode 100644 packages/experience-legacy/src/assets/icons/dev-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/empty-state-dark.svg create mode 100644 packages/experience-legacy/src/assets/icons/empty-state.svg create mode 100644 packages/experience-legacy/src/assets/icons/expand-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/expandable-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/factor-backup-code.svg create mode 100644 packages/experience-legacy/src/assets/icons/factor-totp.svg create mode 100644 packages/experience-legacy/src/assets/icons/factor-webauthn.svg create mode 100644 packages/experience-legacy/src/assets/icons/info-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/loading-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/loading-ring.svg create mode 100644 packages/experience-legacy/src/assets/icons/lock.svg create mode 100644 packages/experience-legacy/src/assets/icons/logto-logo-dark.svg create mode 100644 packages/experience-legacy/src/assets/icons/logto-logo-light.svg create mode 100644 packages/experience-legacy/src/assets/icons/logto-logo-shadow.svg create mode 100644 packages/experience-legacy/src/assets/icons/more-social-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/nav-close.svg create mode 100644 packages/experience-legacy/src/assets/icons/organization-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/password-hide-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/password-show-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/search-icon.svg create mode 100644 packages/experience-legacy/src/assets/icons/switch-icon.svg create mode 100644 packages/experience-legacy/src/assets/index.d.ts create mode 100644 packages/experience-legacy/src/components/BrandingHeader/index.module.scss create mode 100644 packages/experience-legacy/src/components/BrandingHeader/index.test.tsx create mode 100644 packages/experience-legacy/src/components/BrandingHeader/index.tsx create mode 100644 packages/experience-legacy/src/components/Button/IconButton.module.scss create mode 100644 packages/experience-legacy/src/components/Button/IconButton.tsx create mode 100644 packages/experience-legacy/src/components/Button/MfaFactorButton.module.scss create mode 100644 packages/experience-legacy/src/components/Button/MfaFactorButton.tsx create mode 100644 packages/experience-legacy/src/components/Button/RotatingRingIcon.module.scss create mode 100644 packages/experience-legacy/src/components/Button/RotatingRingIcon.tsx create mode 100644 packages/experience-legacy/src/components/Button/SocialLinkButton.module.scss create mode 100644 packages/experience-legacy/src/components/Button/SocialLinkButton.tsx create mode 100644 packages/experience-legacy/src/components/Button/index.module.scss create mode 100644 packages/experience-legacy/src/components/Button/index.test.tsx create mode 100644 packages/experience-legacy/src/components/Button/index.tsx create mode 100644 packages/experience-legacy/src/components/Checkbox/index.module.scss create mode 100644 packages/experience-legacy/src/components/Checkbox/index.tsx create mode 100644 packages/experience-legacy/src/components/ConfirmModal/AcModal.tsx create mode 100644 packages/experience-legacy/src/components/ConfirmModal/Acmodal.module.scss create mode 100644 packages/experience-legacy/src/components/ConfirmModal/MobileModal.module.scss create mode 100644 packages/experience-legacy/src/components/ConfirmModal/MobileModal.tsx create mode 100644 packages/experience-legacy/src/components/ConfirmModal/index.tsx create mode 100644 packages/experience-legacy/src/components/ConfirmModal/type.ts create mode 100644 packages/experience-legacy/src/components/Divider/index.module.scss create mode 100644 packages/experience-legacy/src/components/Divider/index.test.tsx create mode 100644 packages/experience-legacy/src/components/Divider/index.tsx create mode 100644 packages/experience-legacy/src/components/DynamicT/index.test.tsx create mode 100644 packages/experience-legacy/src/components/DynamicT/index.tsx create mode 100644 packages/experience-legacy/src/components/ErrorMessage/index.module.scss create mode 100644 packages/experience-legacy/src/components/ErrorMessage/index.tsx create mode 100644 packages/experience-legacy/src/components/GoogleOneTap/index.tsx create mode 100644 packages/experience-legacy/src/components/IdentifierRegisterForm/index.module.scss create mode 100644 packages/experience-legacy/src/components/IdentifierRegisterForm/index.test.tsx create mode 100644 packages/experience-legacy/src/components/IdentifierRegisterForm/index.tsx create mode 100644 packages/experience-legacy/src/components/IdentifierRegisterForm/use-on-submit.ts create mode 100644 packages/experience-legacy/src/components/IdentifierRegisterForm/use-register-with-username.ts create mode 100644 packages/experience-legacy/src/components/IdentifierSignInForm/index.module.scss create mode 100644 packages/experience-legacy/src/components/IdentifierSignInForm/index.test.tsx create mode 100644 packages/experience-legacy/src/components/IdentifierSignInForm/index.tsx create mode 100644 packages/experience-legacy/src/components/IdentifierSignInForm/use-on-submit.ts create mode 100644 packages/experience-legacy/src/components/InputFields/InputField/NotchedBorder/index.module.scss create mode 100644 packages/experience-legacy/src/components/InputFields/InputField/NotchedBorder/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/InputField/index.module.scss create mode 100644 packages/experience-legacy/src/components/InputFields/InputField/index.test.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/InputField/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/PasswordInputField/index.test.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/PasswordInputField/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/AnimatedPrefix/index.module.scss create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/AnimatedPrefix/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/CountryCodeSelector/CountryCodeDropdown/index.module.scss create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/CountryCodeSelector/CountryCodeDropdown/index.test.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/CountryCodeSelector/CountryCodeDropdown/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/CountryCodeSelector/index.module.scss create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/CountryCodeSelector/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/index.test.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/index.tsx create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/use-smart-input-field.ts create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/utils.test.ts create mode 100644 packages/experience-legacy/src/components/InputFields/SmartInputField/utils.ts create mode 100644 packages/experience-legacy/src/components/InputFields/index.tsx create mode 100644 packages/experience-legacy/src/components/LoadingLayer/LoadingIcon.tsx create mode 100644 packages/experience-legacy/src/components/LoadingLayer/index.module.scss create mode 100644 packages/experience-legacy/src/components/LoadingLayer/index.tsx create mode 100644 packages/experience-legacy/src/components/LoadingMask/index.module.scss create mode 100644 packages/experience-legacy/src/components/LoadingMask/index.tsx create mode 100644 packages/experience-legacy/src/components/LogtoSignature/index.module.scss create mode 100644 packages/experience-legacy/src/components/LogtoSignature/index.tsx create mode 100644 packages/experience-legacy/src/components/NavBar/index.module.scss create mode 100644 packages/experience-legacy/src/components/NavBar/index.tsx create mode 100644 packages/experience-legacy/src/components/Notification/AppNotification/index.module.scss create mode 100644 packages/experience-legacy/src/components/Notification/AppNotification/index.tsx create mode 100644 packages/experience-legacy/src/components/Notification/InlineNotification/index.module.scss create mode 100644 packages/experience-legacy/src/components/Notification/InlineNotification/index.tsx create mode 100644 packages/experience-legacy/src/components/Notification/index.tsx create mode 100644 packages/experience-legacy/src/components/PageMeta/index.tsx create mode 100644 packages/experience-legacy/src/components/PasswordSignInForm/index.module.scss create mode 100644 packages/experience-legacy/src/components/PasswordSignInForm/index.test.tsx create mode 100644 packages/experience-legacy/src/components/PasswordSignInForm/index.tsx create mode 100644 packages/experience-legacy/src/components/SingleSignOnForm/index.module.scss create mode 100644 packages/experience-legacy/src/components/SingleSignOnForm/index.tsx create mode 100644 packages/experience-legacy/src/components/SwitchMfaFactorsLink/index.tsx create mode 100644 packages/experience-legacy/src/components/TermsLinks/index.module.scss create mode 100644 packages/experience-legacy/src/components/TermsLinks/index.tsx create mode 100644 packages/experience-legacy/src/components/TextLink/index.module.scss create mode 100644 packages/experience-legacy/src/components/TextLink/index.test.tsx create mode 100644 packages/experience-legacy/src/components/TextLink/index.tsx create mode 100644 packages/experience-legacy/src/components/Toast/index.module.scss create mode 100644 packages/experience-legacy/src/components/Toast/index.test.tsx create mode 100644 packages/experience-legacy/src/components/Toast/index.tsx create mode 100644 packages/experience-legacy/src/components/VerificationCode/index.module.scss create mode 100644 packages/experience-legacy/src/components/VerificationCode/index.test.tsx create mode 100644 packages/experience-legacy/src/components/VerificationCode/index.tsx create mode 100644 packages/experience-legacy/src/constants/env.ts create mode 100644 packages/experience-legacy/src/containers/DevelopmentTenantNotification/index.tsx create mode 100644 packages/experience-legacy/src/containers/ForgotPasswordLink/index.tsx create mode 100644 packages/experience-legacy/src/containers/MfaFactorList/index.module.scss create mode 100644 packages/experience-legacy/src/containers/MfaFactorList/index.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/HiddenIdentifierInput.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/Lite.test.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/Lite.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/SetPassword.test.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/SetPassword.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/TogglePassword.tsx create mode 100644 packages/experience-legacy/src/containers/SetPassword/index.module.scss create mode 100644 packages/experience-legacy/src/containers/SetPassword/index.tsx create mode 100644 packages/experience-legacy/src/containers/SocialLanding/index.module.scss create mode 100644 packages/experience-legacy/src/containers/SocialLanding/index.tsx create mode 100644 packages/experience-legacy/src/containers/SocialLinkAccount/index.module.scss create mode 100644 packages/experience-legacy/src/containers/SocialLinkAccount/index.test.tsx create mode 100644 packages/experience-legacy/src/containers/SocialLinkAccount/index.tsx create mode 100644 packages/experience-legacy/src/containers/SocialLinkAccount/use-social-link-related-user.ts create mode 100644 packages/experience-legacy/src/containers/SocialSignInList/index.module.scss create mode 100644 packages/experience-legacy/src/containers/SocialSignInList/index.test.tsx create mode 100644 packages/experience-legacy/src/containers/SocialSignInList/index.tsx create mode 100644 packages/experience-legacy/src/containers/SocialSignInList/use-social.ts create mode 100644 packages/experience-legacy/src/containers/TermsAndPrivacyCheckbox/index.module.scss create mode 100644 packages/experience-legacy/src/containers/TermsAndPrivacyCheckbox/index.tsx create mode 100644 packages/experience-legacy/src/containers/TermsAndPrivacyCheckbox/intext.test.tsx create mode 100644 packages/experience-legacy/src/containers/TermsAndPrivacyConfirmModalContent/index.tsx create mode 100644 packages/experience-legacy/src/containers/TermsAndPrivacyLinks/index.tsx create mode 100644 packages/experience-legacy/src/containers/TotpCodeVerification/index.module.scss create mode 100644 packages/experience-legacy/src/containers/TotpCodeVerification/index.tsx create mode 100644 packages/experience-legacy/src/containers/TotpCodeVerification/use-totp-code-verification.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/PasswordSignInLink.tsx create mode 100644 packages/experience-legacy/src/containers/VerificationCode/index.module.scss create mode 100644 packages/experience-legacy/src/containers/VerificationCode/index.test.tsx create mode 100644 packages/experience-legacy/src/containers/VerificationCode/index.tsx create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-continue-flow-code-verification.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-forgot-password-flow-code-verification.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-general-verification-code-error-handler.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-identifier-error-alert.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-link-social-confirm-modal.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-register-flow-code-verification.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-resend-verification-code.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/use-sign-in-flow-code-verification.ts create mode 100644 packages/experience-legacy/src/containers/VerificationCode/utils.ts create mode 100644 packages/experience-legacy/src/favicon.ico create mode 100644 packages/experience-legacy/src/hooks/use-api.ts create mode 100644 packages/experience-legacy/src/hooks/use-check-single-sign-on.ts create mode 100644 packages/experience-legacy/src/hooks/use-confirm-modal.ts create mode 100644 packages/experience-legacy/src/hooks/use-connectors.ts create mode 100644 packages/experience-legacy/src/hooks/use-debounce.ts create mode 100644 packages/experience-legacy/src/hooks/use-error-handler.ts create mode 100644 packages/experience-legacy/src/hooks/use-global-redirect-to.ts create mode 100644 packages/experience-legacy/src/hooks/use-identifier-params.test.ts create mode 100644 packages/experience-legacy/src/hooks/use-identifier-params.ts create mode 100644 packages/experience-legacy/src/hooks/use-login-hint.ts create mode 100644 packages/experience-legacy/src/hooks/use-mfa-error-handler.ts create mode 100644 packages/experience-legacy/src/hooks/use-mfa-factors-state.ts create mode 100644 packages/experience-legacy/src/hooks/use-native-message-listener.ts rename packages/{experience => experience-legacy}/src/hooks/use-password-action.ts (100%) create mode 100644 packages/experience-legacy/src/hooks/use-password-error-message.ts create mode 100644 packages/experience-legacy/src/hooks/use-password-sign-in.ts create mode 100644 packages/experience-legacy/src/hooks/use-platform.ts create mode 100644 packages/experience-legacy/src/hooks/use-pre-sign-in-error-handler.ts create mode 100644 packages/experience-legacy/src/hooks/use-prefilled-identifier.ts create mode 100644 packages/experience-legacy/src/hooks/use-required-profile-error-handler.ts create mode 100644 packages/experience-legacy/src/hooks/use-send-mfa-payload.ts create mode 100644 packages/experience-legacy/src/hooks/use-send-verification-code.ts create mode 100644 packages/experience-legacy/src/hooks/use-session-storages.test.ts create mode 100644 packages/experience-legacy/src/hooks/use-session-storages.ts create mode 100644 packages/experience-legacy/src/hooks/use-sie.ts create mode 100644 packages/experience-legacy/src/hooks/use-single-sign-on-watch.ts create mode 100644 packages/experience-legacy/src/hooks/use-single-sign-on.ts create mode 100644 packages/experience-legacy/src/hooks/use-skip-mfa.ts create mode 100644 packages/experience-legacy/src/hooks/use-social-link-account.ts create mode 100644 packages/experience-legacy/src/hooks/use-social-register.ts create mode 100644 packages/experience-legacy/src/hooks/use-start-totp-binding.ts create mode 100644 packages/experience-legacy/src/hooks/use-start-webauthn-processing.ts create mode 100644 packages/experience-legacy/src/hooks/use-terms.ts create mode 100644 packages/experience-legacy/src/hooks/use-text-handler.ts create mode 100644 packages/experience-legacy/src/hooks/use-toast.ts create mode 100644 packages/experience-legacy/src/hooks/use-toggle.ts create mode 100644 packages/experience-legacy/src/hooks/use-webauthn-operation.ts create mode 100644 packages/experience-legacy/src/i18n/init.ts create mode 100644 packages/experience-legacy/src/i18n/utils.ts create mode 100644 packages/experience-legacy/src/include.d/dom.d.ts create mode 100644 packages/experience-legacy/src/include.d/global.d.ts create mode 100644 packages/experience-legacy/src/include.d/i18next.d.ts create mode 100644 packages/experience-legacy/src/include.d/react-router-dom.d.ts create mode 100644 packages/experience-legacy/src/include.d/vite-env.d.ts create mode 100644 packages/experience-legacy/src/index.tsx create mode 100644 packages/experience-legacy/src/jest.setup.ts create mode 100644 packages/experience-legacy/src/pages/Callback/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Callback/index.tsx create mode 100644 packages/experience-legacy/src/pages/Callback/use-social-callback-handler.ts create mode 100644 packages/experience-legacy/src/pages/Consent/OrganizationSelector/OrganizationItem/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/OrganizationSelector/OrganizationItem/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/OrganizationSelector/OrganizationSelectorModal/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/OrganizationSelector/OrganizationSelectorModal/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/OrganizationSelector/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/OrganizationSelector/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/ScopeGroup/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/ScopeGroup/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/ScopesListCard/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/ScopesListCard/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/UserProfile/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/UserProfile/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Consent/index.tsx create mode 100644 packages/experience-legacy/src/pages/Consent/util.test.ts create mode 100644 packages/experience-legacy/src/pages/Consent/util.ts create mode 100644 packages/experience-legacy/src/pages/Continue/IdentifierProfileForm/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Continue/IdentifierProfileForm/index.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetEmailOrPhone/SocialIdentityNotification.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetEmailOrPhone/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Continue/SetEmailOrPhone/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetEmailOrPhone/index.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetPassword/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetPassword/index.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetUsername/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetUsername/index.tsx create mode 100644 packages/experience-legacy/src/pages/Continue/SetUsername/use-set-username.ts create mode 100644 packages/experience-legacy/src/pages/Continue/index.tsx create mode 100644 packages/experience-legacy/src/pages/DirectSignIn/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/DirectSignIn/index.tsx create mode 100644 packages/experience-legacy/src/pages/ErrorPage/index.module.scss create mode 100644 packages/experience-legacy/src/pages/ErrorPage/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/ErrorPage/index.tsx create mode 100644 packages/experience-legacy/src/pages/ForgotPassword/ForgotPasswordForm/index.module.scss create mode 100644 packages/experience-legacy/src/pages/ForgotPassword/ForgotPasswordForm/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/ForgotPassword/ForgotPasswordForm/index.tsx create mode 100644 packages/experience-legacy/src/pages/ForgotPassword/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/ForgotPassword/index.tsx create mode 100644 packages/experience-legacy/src/pages/IdentifierRegister/index.tsx create mode 100644 packages/experience-legacy/src/pages/IdentifierRegister/use-identifier-sign-up-methods.ts create mode 100644 packages/experience-legacy/src/pages/IdentifierSignIn/index.tsx create mode 100644 packages/experience-legacy/src/pages/IdentifierSignIn/use-identifier-sign-in-methods.ts create mode 100644 packages/experience-legacy/src/pages/MfaBinding/BackupCodeBinding/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaBinding/BackupCodeBinding/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaBinding/TotpBinding/SecretSection/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaBinding/TotpBinding/SecretSection/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaBinding/TotpBinding/VerificationSection.tsx create mode 100644 packages/experience-legacy/src/pages/MfaBinding/TotpBinding/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaBinding/TotpBinding/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaBinding/WebAuthnBinding/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaBinding/WebAuthnBinding/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaBinding/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaVerification/BackupCodeVerification/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaVerification/BackupCodeVerification/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaVerification/TotpVerification/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaVerification/TotpVerification/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaVerification/WebAuthnVerification/index.module.scss create mode 100644 packages/experience-legacy/src/pages/MfaVerification/WebAuthnVerification/index.tsx create mode 100644 packages/experience-legacy/src/pages/MfaVerification/index.tsx create mode 100644 packages/experience-legacy/src/pages/Register/index.module.scss create mode 100644 packages/experience-legacy/src/pages/Register/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/Register/index.tsx create mode 100644 packages/experience-legacy/src/pages/RegisterPassword/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/RegisterPassword/index.tsx create mode 100644 packages/experience-legacy/src/pages/ResetPassword/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/ResetPassword/index.tsx create mode 100644 packages/experience-legacy/src/pages/ResetPasswordLanding/index.tsx create mode 100644 packages/experience-legacy/src/pages/ResetPasswordLanding/use-reset-password-methods.ts create mode 100644 packages/experience-legacy/src/pages/SignIn/Main.tsx create mode 100644 packages/experience-legacy/src/pages/SignIn/index.module.scss create mode 100644 packages/experience-legacy/src/pages/SignIn/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/SignIn/index.tsx create mode 100644 packages/experience-legacy/src/pages/SignInPassword/PasswordForm/VerificationCodeLink.tsx create mode 100644 packages/experience-legacy/src/pages/SignInPassword/PasswordForm/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/SignInPassword/PasswordForm/index.tsx create mode 100644 packages/experience-legacy/src/pages/SignInPassword/index.module.scss create mode 100644 packages/experience-legacy/src/pages/SignInPassword/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/SignInPassword/index.tsx create mode 100644 packages/experience-legacy/src/pages/SingleSignOnConnectors/index.module.scss create mode 100644 packages/experience-legacy/src/pages/SingleSignOnConnectors/index.tsx create mode 100644 packages/experience-legacy/src/pages/SingleSignOnEmail/index.tsx create mode 100644 packages/experience-legacy/src/pages/SingleSignOnLanding/index.tsx create mode 100644 packages/experience-legacy/src/pages/SocialLanding/index.module.scss create mode 100644 packages/experience-legacy/src/pages/SocialLanding/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/SocialLanding/index.tsx create mode 100644 packages/experience-legacy/src/pages/SocialLanding/use-social-landing-handler.ts create mode 100644 packages/experience-legacy/src/pages/SocialLinkAccount/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/SocialLinkAccount/index.tsx create mode 100644 packages/experience-legacy/src/pages/SocialSignInWebCallback/SingleSignOn.tsx create mode 100644 packages/experience-legacy/src/pages/SocialSignInWebCallback/SocialSignIn.tsx create mode 100644 packages/experience-legacy/src/pages/SocialSignInWebCallback/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/SocialSignInWebCallback/index.tsx create mode 100644 packages/experience-legacy/src/pages/SocialSignInWebCallback/use-single-sign-on-listener.ts create mode 100644 packages/experience-legacy/src/pages/SocialSignInWebCallback/use-social-sign-in-listener.ts create mode 100644 packages/experience-legacy/src/pages/Springboard/index.tsx create mode 100644 packages/experience-legacy/src/pages/VerificationCode/index.test.tsx create mode 100644 packages/experience-legacy/src/pages/VerificationCode/index.tsx create mode 100644 packages/experience-legacy/src/scss/_colors.scss create mode 100644 packages/experience-legacy/src/scss/_fonts.scss create mode 100644 packages/experience-legacy/src/scss/_underscore.scss create mode 100644 packages/experience-legacy/src/scss/modal.module.scss create mode 100644 packages/experience-legacy/src/scss/normalized.scss create mode 100644 packages/experience-legacy/src/types/guard.ts create mode 100644 packages/experience-legacy/src/types/index.ts create mode 100644 packages/experience-legacy/src/utils/a11y.ts create mode 100644 packages/experience-legacy/src/utils/consts.ts create mode 100644 packages/experience-legacy/src/utils/cookies.ts create mode 100644 packages/experience-legacy/src/utils/country-code.test.ts create mode 100644 packages/experience-legacy/src/utils/country-code.ts create mode 100644 packages/experience-legacy/src/utils/form.ts create mode 100644 packages/experience-legacy/src/utils/format.test.ts create mode 100644 packages/experience-legacy/src/utils/format.ts create mode 100644 packages/experience-legacy/src/utils/index.test.ts create mode 100644 packages/experience-legacy/src/utils/index.ts create mode 100644 packages/experience-legacy/src/utils/logo.ts create mode 100644 packages/experience-legacy/src/utils/native-sdk.ts create mode 100644 packages/experience-legacy/src/utils/search-parameters.ts create mode 100644 packages/experience-legacy/src/utils/sign-in-experience.test.ts create mode 100644 packages/experience-legacy/src/utils/sign-in-experience.ts create mode 100644 packages/experience-legacy/src/utils/social-connectors.test.ts create mode 100644 packages/experience-legacy/src/utils/social-connectors.ts create mode 100644 packages/experience-legacy/src/utils/webauthn.ts create mode 100644 packages/experience-legacy/tsconfig.json create mode 100644 packages/experience-legacy/vite.config.ts create mode 100644 packages/experience/src/apis/experience/const.ts create mode 100644 packages/experience/src/apis/experience/index.ts create mode 100644 packages/experience/src/apis/experience/interaction.ts create mode 100644 packages/experience/src/apis/experience/mfa.ts create mode 100644 packages/experience/src/apis/experience/social.ts create mode 100644 packages/experience/src/hooks/use-password-policy-checker.ts create mode 100644 packages/experience/src/hooks/use-password-rejection-handler.ts create mode 100644 packages/experience/src/hooks/use-start-backup-code-binding.ts create mode 100644 packages/experience/src/types/guard.test.ts diff --git a/.changeset/fresh-shrimps-rhyme.md b/.changeset/fresh-shrimps-rhyme.md new file mode 100644 index 00000000000..bdd9502a5e4 --- /dev/null +++ b/.changeset/fresh-shrimps-rhyme.md @@ -0,0 +1,12 @@ +--- +"@logto/experience": minor +--- + +migrate experience app using Experience API. + +Migrate experience app API requests from legacy [Interaction API](https://openapi.logto.io/group/endpoint-interaction) to the new [Experience API](https://openapi.logto.io/group/endpoint-experience), except the following endpoints: + +- `GET /api/interaction/consent` +- `POST /api/interaction/consent` + +Those endpoints are used in the third-party application's consent page only. Remain unchanged. diff --git a/package.json b/package.json index 575385f84cd..126199dfdbc 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "prepack": "pnpm -r prepack", "dev": "pnpm -r prepack && pnpm start:dev", "dev:cloud": "IS_CLOUD=1 CONSOLE_PUBLIC_URL=/ pnpm dev", - "start:dev": "pnpm -r --parallel --filter=!@logto/integration-tests --filter \"!./packages/connectors/connector-*\" dev", + "start:dev": "pnpm -r --parallel --filter=!@logto/integration-tests --filter=!@logto/experience-legacy --filter \"!./packages/connectors/connector-*\" dev", "start": "cd packages/core && NODE_ENV=production node .", "cli": "logto", "translate": "logto-translate", diff --git a/packages/core/package.json b/packages/core/package.json index b15c22f037b..8abf8303958 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -39,6 +39,7 @@ "@logto/core-kit": "workspace:^2.5.0", "@logto/demo-app": "workspace:*", "@logto/experience": "workspace:*", + "@logto/experience-legacy": "workspace:*", "@logto/language-kit": "workspace:^1.1.0", "@logto/phrases": "workspace:^1.13.0", "@logto/phrases-experience": "workspace:^1.7.0", diff --git a/packages/core/src/middleware/koa-spa-proxy.ts b/packages/core/src/middleware/koa-spa-proxy.ts index 9ad11eb8f54..71b65058b25 100644 --- a/packages/core/src/middleware/koa-spa-proxy.ts +++ b/packages/core/src/middleware/koa-spa-proxy.ts @@ -22,7 +22,7 @@ type Properties = { export default function koaSpaProxy({ mountedApps, - packagePath = 'experience', + packagePath = EnvSet.values.isDevFeaturesEnabled ? 'experience' : 'experience-legacy', port = 5001, prefix = '', queries, diff --git a/packages/experience-legacy/.eslintrc.cjs b/packages/experience-legacy/.eslintrc.cjs new file mode 100644 index 00000000000..40bcfa69ac0 --- /dev/null +++ b/packages/experience-legacy/.eslintrc.cjs @@ -0,0 +1,22 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: '@silverhand/react', + rules: { + 'jsx-a11y/no-autofocus': 'off', + 'unicorn/prefer-string-replace-all': 'off', + }, + overrides: [ + { + files: ['*.config.js', '*.config.ts', '*.d.ts'], + rules: { + 'import/no-unused-modules': 'off', + }, + }, + { + files: ['*.d.ts'], + rules: { + 'import/no-unassigned-import': 'off', + }, + }, + ], +}; diff --git a/packages/experience-legacy/CHANGELOG.md b/packages/experience-legacy/CHANGELOG.md new file mode 100644 index 00000000000..497cf1bc245 --- /dev/null +++ b/packages/experience-legacy/CHANGELOG.md @@ -0,0 +1,727 @@ +# Change Log + +## 1.8.0 + +### Minor Changes + +- 3a839f6d6: support organization logo and sign-in experience override + + Now it's able to set light and dark logos for organizations. You can upload the logos in the organization settings page. + + Also, it's possible to override the sign-in experience logo from an organization. Simply add the `organization_id` parameter to the authentication request. In most Logto SDKs, it can be done by using the `extraParams` field in the `signIn` method. + + For example, in the JavaScript SDK: + + ```ts + import LogtoClient from "@logto/client"; + + const logtoClient = new LogtoClient(/* your configuration */); + + logtoClient.signIn({ + redirectUri: "https://your-app.com/callback", + extraParams: { + organization_id: "", + }, + }); + ``` + + The value `` can be found in the organization settings page. + + If you could not find the `extraParams` field in the SDK you are using, please let us know. + +- 62f5e5e0c: support app-level branding + + You can now set logos, favicons, and colors for your app. These settings will be used in the sign-in experience when the app initiates the authentication flow. For apps that have no branding settings, the omni sign-in experience branding will be used. + + If `organization_id` is provided in the authentication request, the app-level branding settings will be overridden by the organization's branding settings, if available. + +- d203c8d2f: support experience data server-side rendering + + Logto now injects the sign-in experience settings and phrases into the `index.html` file for better first-screen performance. The experience app will still fetch the settings and phrases from the server if: + + - The server didn't inject the settings and phrases. + - The parameters in the URL are different from server-rendered data. + +- 3bf756f2b: use Vite for transpilation and bundling + + Removed ParcelJS and replaced with Vite. No breaking changes should be expected, but use a minor version bump to catch your attention. + + > [!Important] + > The browserlist configuration for `@logto/experience` and been synced with what is stated in README.md. + +- 62f5e5e0c: support dark favicon + + The favicon for the dark theme now can be set in the sign-in experience branding settings. + +## 1.7.0 + +### Minor Changes + +- 061a30a87: support agree to terms polices for Logto’s sign-in experiences + + - Automatic: Users automatically agree to terms by continuing to use the service + - ManualRegistrationOnly: Users must agree to terms by checking a box during registration, and don't need to agree when signing in + - Manual: Users must agree to terms by checking a box during registration or signing in + +- 50c35a214: support Google One Tap + + - Conditionally load Google One Tap script if it's enabled in the config. + - Support callback from Google One Tap. + +### Patch Changes + +- 136320584: allow skipping manual account linking during sign-in + + You can find this configuration in Console -> Sign-in experience -> Sign-up and sign-in -> Social sign-in -> Automatic account linking. + + When switched on, if a user signs in with a social identity that is new to the system, and there is exactly one existing account with the same identifier (e.g., email), Logto will automatically link the account with the social identity instead of prompting the user for account linking. + +## 1.6.2 + +### Patch Changes + +- cb1a38c40: show global loading icon on page relocate + + This is to address the issue where the user is redirected back to the client after a successful login, but the page is not yet fully loaded. This will show a global loading icon to indicate that the page is still loading. Preventing the user from interacting with the current sign-in page and avoid page idling confusion. + +## 1.6.1 + +### Patch Changes + +- b80934ac5: fix native social sign-in callback + + In a native environment, the social sign-in callback that posts to the native container (e.g. WKWebView in iOS) was wrong. + + This was introduced by a refactor in #5536: It updated the callback path from `/sign-in/social/:connectorId` to `/callback/social/:connectorId`. However, the function to post the message to the native container was not updated accordingly. + +- bbd399e15: fix the new user from SSO register hook event not triggering bug + + ### Issue + + When a new user registers via SSO, the `PostRegister` interaction hook event is not triggered. `PostSignIn` event is mistakenly triggered instead. + + ### Root Cause + + In the SSO `post /api/interaction/sso/:connectionId/registration` API, we update the interaction event to `Register`. + However, the hook middleware reads the event from interaction session ahead of the API logic, and the event is not updated resulting in the wrong event being triggered. + + In the current interaction API design, we should mutate the interaction event by calling the `PUT /api/interaction/event` API, instead of updating the event directly in the submit interaction APIs. (Just like the no direct mutation rule for a react state). So we can ensure the correct side effect like logs and hooks are triggered properly. + + All the other sign-in methods are using the `PUT /api/interaction/event` API to update the event. But when implementing the SSO registration API, we were trying to reduce the API requests and directly updated the event in the registration API which will submit the interaction directly. + + ### Solution + + Remove the event update logic in the SSO registration API and call the `PUT /api/interaction/event` API to update the event. + This will ensure the correct event is triggered in the hook middleware. + + ### Action Items + + Align the current interaction API design for now. + Need to improve the session/interaction API logic to simplify the whole process. + +## 1.6.0 + +### Minor Changes + +- 7756f50f8: support direct sign-in for sso +- 2cbc591ff: support direct sign-in + + Instead of showing a screen for the user to choose between the sign-in methods, a specific sign-in method can be initiated directly by setting the `direct_sign_in` parameter in the OIDC authentication request. + + This parameter follows the format of `direct_sign_in=:`, where: + + - `` is the sign-in method to trigger. Currently the only supported value is `social`. + - `` is the target value for the sign-in method. If the method is `social`, the value is the social connector's `target`. + + When a valid `direct_sign_in` parameter is set, the first screen will be skipped and the specified sign-in method will be triggered immediately upon entering the sign-in experience. If the parameter is invalid, the default behavior of showing the first screen will be used. + +### Patch Changes + +- 5a7204571: skip non-object messages in the native environment + + In the `WKWebView` of new iOS versions, some script will constantly post messages to the + window object with increasing numbers as the message content ("1", "2", "3", ...). + + Ideally, we should check the source of the message with Logto-specific identifier in the + `event.data`; however, this change will result a breaking change for the existing + native SDK implementations. Add the `isObject` check to prevent the crazy messages while + keeping the backward compatibility. + +## 1.5.0 + +### Minor Changes + +- 32df9acde: update user consent page to support the new third-party application feature + + - Only show the user consent page if current application is a third-party application, otherwise auto-consent the requested scopes. + - Add the new fetching API to get the user consent context. Including the application detail, authenticated user info, all the requested scopes and user organizations info (if requested scopes include the organization scope). + - Add the new user consent interaction API and authorize button. User have to manually authorize the requested scopes for the third-party application before continue the authentication flow. + +- 31e60811d: use Node 20 LTS for engine requirement. + + Note: We mark it as minor because Logto is shipping with Docker image and it's not a breaking change for users. + +### Patch Changes + +- 9089dbf84: upgrade TypeScript to 5.3.3 + +## 1.4.0 + +### Minor Changes + +- 9a7b19e49: Implement the new single sign-on (SSO) interaction flow + + - `/single-sign-on/email` - The SSO email form page for user to enter their email address. + - `/single-sign-on/connectors` - The SSO connectors page for user to select the enabled SSO connector they want to use. + - Implement the email identifier guard on all the sign-in and registration identifier forms. If the email address is enabled with SSO, redirect user to the SSO flow. + +### Patch Changes + +- 9421375d7: Bump libphonenumber-js to v1.10.51 to support China 19 started phone numbers. Thanks to @agileago + +## 1.3.0 + +### Minor Changes + +- 6727f629d: feature: introduce multi-factor authentication + + We're excited to announce that Logto now supports multi-factor authentication (MFA) for your sign-in experience. Navigate to the "Multi-factor auth" tab to configure how you want to secure your users' accounts. + + In this release, we introduce the following MFA methods: + + - Authenticator app OTP: users can add any authenticator app that supports the TOTP standard, such as Google Authenticator, Duo, etc. + - WebAuthn (Passkey): users can use the standard WebAuthn protocol to register a hardware security key, such as biometric keys, Yubikey, etc. + - Backup codes:users can generate a set of backup codes to use when they don't have access to other MFA methods. + + For a smooth transition, we also support to configure the MFA policy to require MFA for sign-in experience, or to allow users to opt-in to MFA. + +## 1.2.1 + +### Patch Changes + +- 6f5a0acad: fix a bug that prevents user from customizing i18n translations in Sign-in Experience config + +## 1.2.0 + +### Minor Changes + +- e8b0b1d02: feature: password policy + + ### Summary + + This feature enables custom password policy for users. Now it is possible to guard with the following rules when a user is creating a new password: + + - Minimum length (default: `8`) + - Minimum character types (default: `1`) + - If the password has been pwned (default: `true`) + - If the password is exactly the same as or made up of the restricted phrases: + - Repetitive or sequential characters (default: `true`) + - User information (default: `true`) + - Custom words (default: `[]`) + + If you are an existing Logto Cloud user or upgrading from a previous version, to ensure a smooth experience, we'll keep the original policy as much as possible: + + > The original password policy requires a minimum length of 8 and at least 2 character types (letters, numbers, and symbols). + + Note in the new policy implementation, it is not possible to combine lower and upper case letters into one character type. So the original password policy will be translated into the following: + + - Minimum length: `8` + - Minimum character types: `2` + - Pwned: `false` + - Repetitive or sequential characters: `false` + - User information: `false` + - Custom words: `[]` + + If you want to change the policy, you can do it: + + - Logto Console -> Sign-in experience -> Password policy. + - Update `passwordPolicy` property in the sign-in experience via Management API. + + ### Side effects + + - All new users will be affected by the new policy immediately. + - Existing users will not be affected by the new policy until they change their password. + - We removed password restrictions when adding or updating a user via Management API. + +### Patch Changes + +- f8408fa77: rename the package `phrases-ui` to `phrases-experience` +- f6723d5e2: rename the package `ui` to `experience` + +## 1.1.5 + +### Patch Changes + +- c743cef42: Bug fix main flow preview mode should not allow user interaction. + + - Recover the missing preview classname from the preview mode body element + +## 1.1.4 + +### Patch Changes + +- 046a5771b: upgrade i18next series packages (#3733, #3743) + +## 1.1.3 + +### Patch Changes + +- 748878ce5: add React context and hook to app-insights, fix init issue for frontend projects + +## 1.1.2 + +### Patch Changes + +- 352807b16: support setting cloud role name for AppInsights in React + +## 1.1.1 + +### Patch Changes + +- 4945b0be2: Apply security headers + + Apply security headers to logto http request response using (helmetjs)[https://helmetjs.github.io/]. + + - [x] crossOriginOpenerPolicy + - [x] crossOriginEmbedderPolicy + - [x] crossOriginResourcePolicy + - [x] hidePoweredBy + - [x] hsts + - [x] ieNoOpen + - [x] noSniff + - [x] referrerPolicy + - [x] xssFilter + - [x] Content-Security-Policy + +## 1.1.0 + +## 1.0.3 + +## 1.0.2 + +## 1.0.1 + +## 1.0.0 + +### Major Changes + +- 1c9160112: ### Features + + - Enhanced user search params #2639 + - Web hooks + + ### Improvements + + - Refactored Interaction APIs and Audit logs + +- 343b1090f: **💥 BREAKING CHANGE 💥** Move `/api/phrase` API to `/api/.well-known/phrases` + +### Minor Changes + +- 343b1090f: ### Simplify the terms of use and privacy policy manual agreement steps for the sign-in flow + + The Terms of Use and Privacy Policy manuel agreement are now removed from the sign-in flow. + + - The changes may take effect on all the existing sign-in flows, including password sign-in, social sign-in, and verification-code sign-in. + - The agreement checkbox in sign-in pages is now replaced with links to the Terms of Use and Privacy Policy pages. Users can still read the agreements before signing in. + - The manual agreement steps are still mandatory for the sign-up flow. Users must agree to the Terms of Use and Privacy Policy before signing up a new account. Including sign-up with new social identities. The agreement checkbox in sign-up pages remain still. + +- f41fd3f05: Replace `passcode` naming convention in the interaction APIs and main flow ui with `verificationCode`. +- 343b1090f: ### Update the password policy + + Password policy description: Password requires a minimum of 8 characters and contains a mix of letters, numbers, and symbols. + + - min-length updates: Password requires a minimum of 8 characters + - allowed characters updates: Password contains a mix of letters, numbers, and symbols + - digits: 0-9 + - letters: a-z, A-Z + - symbols: !"#$%&'()\*+,./:;<=>?@[\]^\_`{|}~- + - At least two types of characters are required: + - letters and digits + - letters and symbols + - digits and symbols + + > notice: The new password policy is applied to new users or new passwords only. Existing users are not affected by this change, users may still use their old password to sign-in. + +- 343b1090f: ### Add dynamic favicon and html title + + - Add the favicon field in the sign-in-experience branding settings. Users would be able to upload their own favicon. Use local logto icon as a fallback + + - Set different html title for different pages. + - sign-in + - register + - forgot-password + - logto + +- 343b1090f: Allow admin tenant admin to create tenants without limitation +- 343b1090f: ## Add iframe modal for mobile platform + + Implement a full screen iframe modal on the mobile platform. As for most of the webview containers, opening a new tab is not allowed. So we need to implement a full screen iframe modal to show the external link page on the mobile platform. + +- 343b1090f: New feature: User account settings page + + - We have removed the previous settings page and moved it to the account settings page. You can access to the new settings menu by clicking the user avatar in the top right corner. + - You can directly change the language or theme from the popover menu, and explore more account settings by clicking the "Profile" menu item. + - You can update your avatar, name and username in the profile page, and also changing your password. + - [Cloud] Cloud users can also link their email address and social accounts (Google and GitHub at first launch). + +- c12717412: ## Smart Identifier Input designed to streamline your sign-in experience + + - Smart Contact Input + - Smart Identifier Input + - Intelligent Identifier Input Field + + Content: + We have integrated the traditional input fields for username, phone number, and email into a single intelligent input box. This advanced input box automatically identifies the type of characters you’re entering, such as an @ sign or consecutive numbers, and provides relevant error feedback. By streamlining the sign-in process, users no longer need to waste time figuring out which button to click to switch their desired login method. This reduces the risk of errors and ensures a smoother sign-in experience. + +- 343b1090f: Implement a country code selector dropdown component with search box. Users may able to quick search for a country code by typing in the search box. +- 343b1090f: remove the branding style config and make the logo URL config optional +- c12717412: **Customize CSS for Sign-in Experience** + + We have put a lot of effort into improving the user sign-in experience and have provided a brand color option for the UI. However, we know that fine-tuning UI requirements can be unpredictable. While Logto is still exploring the best options for customization, we want to provide a programmatic method to unblock your development. + + You can now use the Management API `PATCH /api/sign-in-exp` with body `{ "customCss": "arbitrary string" }` to set customized CSS for the sign-in experience. You should see the value of `customCss` attached after `` of the page. If the style has a higher priority, it should be able to override. + + > **Note** + > + > Since Logto uses CSS Modules, you may see a hash value in the `class` property of DOM elements (e.g. a `<div>` with `vUugRG_container`). To override these, you can use the `$=` CSS selector to match elements that end with a specified value. In this case, it should be `div[class$=container]`. + +- 343b1090f: Add custom CSS code editor so that users can apply advanced UI customization. + - Users can check the real time preview of the CSS via SIE preview on the right side. +- 2168936b9: **Sign-in Experience v2** + + We are thrilled to announce the release of the newest version of the Sign-in Experience, which includes more ways to sign-in and sign-up, as well as a framework that is easier to understand and more flexible to configure in the Admin Console. + + When compared to Sign-in Experience v1, this version’s capability was expanded so that it could support a greater variety of flexible use cases. For example, now users can sign up with email verification code and sign in with email and password. + + We hope that this will be able to assist developers in delivering a successful sign-in flow, which will also be appreciated by the end users. + +- 343b1090f: ### Add custom content sign-in-experience settings to allow insert custom static html content to the logto sign-in pages + + - feat: combine with the custom css, give the user the ability to further customize the sign-in pages + +- fdb2bb48e: **Streamlining the social sign-up flow** + + - detect trusted email (or phone number) from the social account + - email (or phone number) has been registered: automatically connecting the social identity to the existing user account with a single click + - email (or phone number) not registered: automatically sync up the user profile with the social provided email (or phone) if and only if marked as a required user profile. + +- f41fd3f05: Replace the `sms` naming convention using `phone` cross logto codebase. Including Sign-in Experience types, API paths, API payload and internal variable names. + +### Patch Changes + +- 51f527b0c: bug fixes + + - core: fix 500 error when enabling app admin access in console + - ui: handle required profile errors on social binding flow + +- 343b1090f: ## Implement a lite version of set password form. + + To simplify the effort when user set new password, we implement a lite version of set password form. + + The lite version of set password form only contains only one field password. It will be used if and only if the forgot-password feature is enabled (password can be reset either by email and phone). + + If you do not have any email or sms service enabled, we still use the old version of set password form which contains two fields: password and confirm password. + +- 38970fb88: Fix a Sign-in experience bug that may block some users to sign in. +- 02cc9abd8: Fix a bug to show forgot password when only SMS connector is configured +- 343b1090f: - Add Power By Logto Signature to the main-flow pages + +## 1.0.0-rc.3 + +## 1.0.0-rc.2 + +### Minor Changes + +- c12717412: ## Smart Identifier Input designed to streamline your sign-in experience + + - Smart Contact Input + - Smart Identifier Input + - Intelligent Identifier Input Field + + Content: + We have integrated the traditional input fields for username, phone number, and email into a single intelligent input box. This advanced input box automatically identifies the type of characters you’re entering, such as an @ sign or consecutive numbers, and provides relevant error feedback. By streamlining the sign-in process, users no longer need to waste time figuring out which button to click to switch their desired login method. This reduces the risk of errors and ensures a smoother sign-in experience. + +- c12717412: **Customize CSS for Sign-in Experience** + + We have put a lot of effort into improving the user sign-in experience and have provided a brand color option for the UI. However, we know that fine-tuning UI requirements can be unpredictable. While Logto is still exploring the best options for customization, we want to provide a programmatic method to unblock your development. + + You can now use the Management API `PATCH /api/sign-in-exp` with body `{ "customCss": "arbitrary string" }` to set customized CSS for the sign-in experience. You should see the value of `customCss` attached after `<title>` of the page. If the style has a higher priority, it should be able to override. + + > **Note** + > + > Since Logto uses CSS Modules, you may see a hash value in the `class` property of DOM elements (e.g. a `<div>` with `vUugRG_container`). To override these, you can use the `$=` CSS selector to match elements that end with a specified value. In this case, it should be `div[class$=container]`. + +## 1.0.0-rc.1 + +### Patch Changes + +- 51f527b0: bug fixes + + - core: fix 500 error when enabling app admin access in console + - ui: handle required profile errors on social binding flow + +## 1.0.0-rc.0 + +### Minor Changes + +- f41fd3f0: Replace `passcode` naming convention in the interaction APIs and main flow ui with `verificationCode`. +- fdb2bb48: **Streamlining the social sign-up flow** + + - detect trusted email (or phone number) from the social account + - email (or phone number) has been registered: automatically connecting the social identity to the existing user account with a single click + - email (or phone number) not registered: automatically sync up the user profile with the social provided email (or phone) if and only if marked as a required user profile. + +- f41fd3f0: Replace the `sms` naming convention using `phone` cross logto codebase. Including Sign-in Experience types, API paths, API payload and internal variable names. + +## 1.0.0-beta.19 + +## 1.0.0-beta.18 + +### Major Changes + +- 1c916011: ### Features + + - Enhanced user search params #2639 + - Web hooks + + ### Improvements + + - Refactored Interaction APIs and Audit logs + +## 1.0.0-beta.17 + +### Patch Changes + +- 02cc9abd: Fix a bug to show forgot password when only SMS connector is configured + +## 1.0.0-beta.16 + +### Patch Changes + +- 38970fb8: Fix a Sign-in experience bug that may block some users to sign in. + +## 1.0.0-beta.15 + +## 1.0.0-beta.14 + +## 1.0.0-beta.13 + +### Minor Changes + +- 2168936b: **Sign-in Experience v2** + + We are thrilled to announce the release of the newest version of the Sign-in Experience, which includes more ways to sign-in and sign-up, as well as a framework that is easier to understand and more flexible to configure in the Admin Console. + + When compared to Sign-in Experience v1, this version’s capability was expanded so that it could support a greater variety of flexible use cases. For example, now users can sign up with email verification code and sign in with email and password. + + We hope that this will be able to assist developers in delivering a successful sign-in flow, which will also be appreciated by the end users. + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.0.0-beta.12](https://github.com/logto-io/logto/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2022-10-19) + +**Note:** Version bump only for package @logto/ui + +## [1.0.0-beta.11](https://github.com/logto-io/logto/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2022-10-19) + +### Features + +- **ui:** add a11y support ([#2076](https://github.com/logto-io/logto/issues/2076)) ([2249d71](https://github.com/logto-io/logto/commit/2249d717a8928597d00c383c268d6fdc506ac437)) +- **ui:** add reset password error handling flow ([#2079](https://github.com/logto-io/logto/issues/2079)) ([afa2ac4](https://github.com/logto-io/logto/commit/afa2ac47ee461e3526f61594e456d484fd3166af)) +- **ui:** global confirm modal ([#2018](https://github.com/logto-io/logto/issues/2018)) ([f1ca49c](https://github.com/logto-io/logto/commit/f1ca49c89253daef8b47ec88e30f69df818374d1)) + +### Bug Fixes + +- **console:** remove connector id and prevent text overflow ([#2072](https://github.com/logto-io/logto/issues/2072)) ([05b5025](https://github.com/logto-io/logto/commit/05b50250a387635649614aaeeec9757e7034a19d)) +- **ui:** fix ut ([9ea6a8c](https://github.com/logto-io/logto/commit/9ea6a8c8e94e116d8efbbff63b39738162cbaec1)) +- **ui:** revert color token changes in ui as it uses different design system ([489e2b3](https://github.com/logto-io/logto/commit/489e2b3a1129fbbf824955e4697c1d64ff294d95)) + +## [1.0.0-beta.10](https://github.com/logto-io/logto/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2022-09-28) + +### Features + +- **ui:** add forget password flow ([#1952](https://github.com/logto-io/logto/issues/1952)) ([ba787b4](https://github.com/logto-io/logto/commit/ba787b434ba4dd43064c56115eabfdba9912f98a)) +- **ui:** add forget password page ([#1943](https://github.com/logto-io/logto/issues/1943)) ([39d80d9](https://github.com/logto-io/logto/commit/39d80d991235c93346c26977541d3c7040379a13)) +- **ui:** add passwordless switch ([#1976](https://github.com/logto-io/logto/issues/1976)) ([ddb0e47](https://github.com/logto-io/logto/commit/ddb0e47950b3bd7f92af2a8a5e14b201e0a10ed7)) +- **ui:** add reset password form ([#1964](https://github.com/logto-io/logto/issues/1964)) ([f97ec56](https://github.com/logto-io/logto/commit/f97ec56fbf169538cff5f8f23ed8bb67e9483b27)) +- **ui:** add reset password page ([#1961](https://github.com/logto-io/logto/issues/1961)) ([ff81b0f](https://github.com/logto-io/logto/commit/ff81b0f83e86dd3686341d3612f3f5e8f075cba6)) + +### Bug Fixes + +- bump react sdk and essentials toolkit to support CJK characters in idToken ([2f92b43](https://github.com/logto-io/logto/commit/2f92b438644bd330fa4b8cd3698d9129ecbae282)) +- **ui:** align mobile input outline ([#1991](https://github.com/logto-io/logto/issues/1991)) ([c9ba198](https://github.com/logto-io/logto/commit/c9ba198b59ae52d3c5b4520a98864519d7a756f7)) + +## [1.0.0-beta.9](https://github.com/logto-io/logto/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2022-09-07) + +### Features + +- add Portuguese translation ([f268ecb](https://github.com/logto-io/logto/commit/f268ecb1a8d57d1e33225bec8852f3bc377dd478)) + +### Bug Fixes + +- **console,ui:** fix locale guard issue in settings page ([e200578](https://github.com/logto-io/logto/commit/e2005780a39fa7b5f5c5e406f37805913b684c18)) + +## [1.0.0-beta.8](https://github.com/logto-io/logto/compare/v1.0.0-beta.6...v1.0.0-beta.8) (2022-09-01) + +**Note:** Version bump only for package @logto/ui + +## [1.0.0-beta.6](https://github.com/logto-io/logto/compare/v1.0.0-beta.5...v1.0.0-beta.6) (2022-08-30) + +**Note:** Version bump only for package @logto/ui + +## [1.0.0-beta.5](https://github.com/logto-io/logto/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2022-08-19) + +**Note:** Version bump only for package @logto/ui + +## [1.0.0-beta.4](https://github.com/logto-io/logto/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2022-08-11) + +### Bug Fixes + +- build and types ([8b51543](https://github.com/logto-io/logto/commit/8b515435cdb0644d0ca19e2c26ba3e744355bb0b)) +- **ui,console,demo-app:** update react render method ([#1750](https://github.com/logto-io/logto/issues/1750)) ([4b972f2](https://github.com/logto-io/logto/commit/4b972f2e23e2d4609d9955c4d1d42972f368f5b9)) +- **ui:** add sandbox props to iframe ([#1757](https://github.com/logto-io/logto/issues/1757)) ([62d2afe](https://github.com/logto-io/logto/commit/62d2afe9579334547b7ff5b803299b89933a5bd8)) +- **ui:** connector name should fallback to en ([#1718](https://github.com/logto-io/logto/issues/1718)) ([3af5b1b](https://github.com/logto-io/logto/commit/3af5b1b4250d6de6883b4c8a8b9f7cf4f9b12dab)) +- **ui:** extract ReactModal elementApp and fix act warning in ut ([#1756](https://github.com/logto-io/logto/issues/1756)) ([0270bf1](https://github.com/logto-io/logto/commit/0270bf1be3a51d9b9f8ed84a0327c58ed8a1bd4d)) +- **ui:** fix ui test ([e4629f2](https://github.com/logto-io/logto/commit/e4629f2a5fd26a1d8eaefd04042eaeb5563ec30c)) + +## [1.0.0-beta.3](https://github.com/logto-io/logto/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2022-08-01) + +### Features + +- **phrases:** tr language ([#1707](https://github.com/logto-io/logto/issues/1707)) ([411a8c2](https://github.com/logto-io/logto/commit/411a8c2fa2bfb16c4fef5f0a55c3c1dc5ead1124)) + +## [1.0.0-beta.2](https://github.com/logto-io/logto/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2022-07-25) + +### Bug Fixes + +- **ui:** fix some firefox standout bug ([#1615](https://github.com/logto-io/logto/issues/1615)) ([4ce6bd8](https://github.com/logto-io/logto/commit/4ce6bd8cf5c5953d9f62878ab2ea6ede74f6ca48)) +- **ui:** protect window.location xss ([#1639](https://github.com/logto-io/logto/issues/1639)) ([34b465c](https://github.com/logto-io/logto/commit/34b465c7d83999e2215ef83555b64e38778b8b49)) +- **ui:** should clear prev passcode input when click on backspace ([#1660](https://github.com/logto-io/logto/issues/1660)) ([7dfbc30](https://github.com/logto-io/logto/commit/7dfbc300b09cc3dac2a06176bf2cbc9f338d857e)) + +## [1.0.0-beta.1](https://github.com/logto-io/logto/compare/v1.0.0-beta.0...v1.0.0-beta.1) (2022-07-19) + +### Features + +- **ui:** add submit input to all the sign-in & register forms ([#1587](https://github.com/logto-io/logto/issues/1587)) ([0c0c83c](https://github.com/logto-io/logto/commit/0c0c83cc8f78f611f5a8527ecedd6ce21d1dad80)) + +### Bug Fixes + +- **ui:** fix no-restrict-syntax in ui ([#1559](https://github.com/logto-io/logto/issues/1559)) ([816ce9f](https://github.com/logto-io/logto/commit/816ce9f903fc939b676165c5ad7e17c72f4c1c86)) +- **ui:** format phone number with country calling code ([#1551](https://github.com/logto-io/logto/issues/1551)) ([c6384be](https://github.com/logto-io/logto/commit/c6384bed84340909aaa41f10abaea26b5195e6a5)) + +## [1.0.0-beta.0](https://github.com/logto-io/logto/compare/v1.0.0-alpha.4...v1.0.0-beta.0) (2022-07-14) + +### Bug Fixes + +- **ui,core:** fix i18n issue ([#1548](https://github.com/logto-io/logto/issues/1548)) ([6b58d8a](https://github.com/logto-io/logto/commit/6b58d8a1610b1b75155d873e8898786d2b723ec6)) +- **ui:** fix multiple libphonmenumber packed bug ([#1544](https://github.com/logto-io/logto/issues/1544)) ([e06f8d0](https://github.com/logto-io/logto/commit/e06f8d027eaea3ab89b4fd301be46af3508b61b5)) + +## [1.0.0-alpha.4](https://github.com/logto-io/logto/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2022-07-08) + +### Bug Fixes + +- **ui:** add form submit event ([#1489](https://github.com/logto-io/logto/issues/1489)) ([f52fa58](https://github.com/logto-io/logto/commit/f52fa5891d70bf9a50c76eb3efa35f6031dc88cb)) + +## [1.0.0-alpha.3](https://github.com/logto-io/logto/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2022-07-07) + +### Bug Fixes + +- **core,ui:** remove todo comments ([#1454](https://github.com/logto-io/logto/issues/1454)) ([d5d6c5e](https://github.com/logto-io/logto/commit/d5d6c5ed083364dabaa0220deaa6a22e0350d146)) + +## [1.0.0-alpha.2](https://github.com/logto-io/logto/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2022-07-07) + +### Bug Fixes + +- **ui:** dark mode seed ([#1426](https://github.com/logto-io/logto/issues/1426)) ([be73dbf](https://github.com/logto-io/logto/commit/be73dbf4ef14cf49779775dd95848ba73904a4b2)) +- **ui:** set ui specific i18n storage key ([#1441](https://github.com/logto-io/logto/issues/1441)) ([5b121d7](https://github.com/logto-io/logto/commit/5b121d78551d471125737daf31d4e0505e69e409)) + +## [1.0.0-alpha.1](https://github.com/logto-io/logto/compare/v1.0.0-alpha.0...v1.0.0-alpha.1) (2022-07-05) + +**Note:** Version bump only for package @logto/ui + +## [1.0.0-alpha.0](https://github.com/logto-io/logto/compare/v0.1.2-alpha.5...v1.0.0-alpha.0) (2022-07-04) + +**Note:** Version bump only for package @logto/ui + +### [0.1.2-alpha.5](https://github.com/logto-io/logto/compare/v0.1.2-alpha.4...v0.1.2-alpha.5) (2022-07-03) + +**Note:** Version bump only for package @logto/ui + +### [0.1.2-alpha.4](https://github.com/logto-io/logto/compare/v0.1.2-alpha.3...v0.1.2-alpha.4) (2022-07-03) + +**Note:** Version bump only for package @logto/ui + +### [0.1.2-alpha.3](https://github.com/logto-io/logto/compare/v0.1.2-alpha.2...v0.1.2-alpha.3) (2022-07-03) + +**Note:** Version bump only for package @logto/ui + +### [0.1.2-alpha.2](https://github.com/logto-io/logto/compare/v0.1.2-alpha.1...v0.1.2-alpha.2) (2022-07-02) + +**Note:** Version bump only for package @logto/ui + +### [0.1.2-alpha.1](https://github.com/logto-io/logto/compare/v0.1.2-alpha.0...v0.1.2-alpha.1) (2022-07-02) + +**Note:** Version bump only for package @logto/ui + +### [0.1.2-alpha.0](https://github.com/logto-io/logto/compare/v0.1.1-alpha.0...v0.1.2-alpha.0) (2022-07-02) + +**Note:** Version bump only for package @logto/ui + +### [0.1.1-alpha.0](https://github.com/logto-io/logto/compare/v0.1.0-internal...v0.1.1-alpha.0) (2022-07-01) + +### Features + +- **connector:** apple ([#966](https://github.com/logto-io/logto/issues/966)) ([7400ed8](https://github.com/logto-io/logto/commit/7400ed8896fdceda6165a0540413efb4e3a47438)) +- **console,ui:** generate dark mode color in console ([#1231](https://github.com/logto-io/logto/issues/1231)) ([f72b21d](https://github.com/logto-io/logto/commit/f72b21d1602ab0fb35ef3e7d84f6c8ebd7e18b08)) +- **console:** add 404 page in admin console ([0d047fb](https://github.com/logto-io/logto/commit/0d047fbaf115f005615b5df06170e526283d9335)) +- **console:** add mobile web tab in preview ([#1214](https://github.com/logto-io/logto/issues/1214)) ([9b6fd4c](https://github.com/logto-io/logto/commit/9b6fd4c417f2ee53375e436c839b711c86403d58)) +- **console:** sie form reorg ([#1218](https://github.com/logto-io/logto/issues/1218)) ([2c41334](https://github.com/logto-io/logto/commit/2c413341d1c515049faa130416f7a5e591d10e8a)) +- **core,connectors:** update Aliyun logo and add logo_dark to Apple, Github ([#1194](https://github.com/logto-io/logto/issues/1194)) ([98f8083](https://github.com/logto-io/logto/commit/98f808320b1c79c51f8bd6f49e35ca44363ea560)) +- **core,console:** social connector targets ([#851](https://github.com/logto-io/logto/issues/851)) ([127664a](https://github.com/logto-io/logto/commit/127664a62f1b1c794569b7fe9d0bfceb7b97dc74)) +- **core:** add sign-in-mode ([#1132](https://github.com/logto-io/logto/issues/1132)) ([f640dad](https://github.com/logto-io/logto/commit/f640dad52f2e75620b392114673860138e1aca2c)) +- **core:** add socialConnectors details for get sign-in-settings ([#804](https://github.com/logto-io/logto/issues/804)) ([7a922cb](https://github.com/logto-io/logto/commit/7a922cbd331b45443f7f19a8af3dcd9156453079)) +- **core:** update connector db schema ([#732](https://github.com/logto-io/logto/issues/732)) ([8e1533a](https://github.com/logto-io/logto/commit/8e1533a70267d459feea4e5174296b17bef84d48)) +- **demo-app:** show notification in main flow ([#1038](https://github.com/logto-io/logto/issues/1038)) ([90ca76e](https://github.com/logto-io/logto/commit/90ca76eeb5460b66d2241f137f179bf4d5d6ae37)) +- **ui:** add bind social account flow ([#671](https://github.com/logto-io/logto/issues/671)) ([5e251bd](https://github.com/logto-io/logto/commit/5e251bdc0818195d7eb104488bdb8a3194205bdd)) +- **ui:** add darkmode logo ([#880](https://github.com/logto-io/logto/issues/880)) ([9fa13a2](https://github.com/logto-io/logto/commit/9fa13a24ae2e1b3024b3ef2169736d27847f04eb)) +- **ui:** add global primary color settings ([#871](https://github.com/logto-io/logto/issues/871)) ([0f2827c](https://github.com/logto-io/logto/commit/0f2827ccb873bf30e44209da39803ac6cc839af2)) +- **ui:** add mobile terms of use iframe modal ([#947](https://github.com/logto-io/logto/issues/947)) ([4abcda6](https://github.com/logto-io/logto/commit/4abcda6820f0d824d110ee3ddd6d457433dfbf26)) +- **ui:** add native sdk guard logic ([#1096](https://github.com/logto-io/logto/issues/1096)) ([147775a](https://github.com/logto-io/logto/commit/147775a8f45dbb5bbf05b3bf1b7c11c0a8acf4a4)) +- **ui:** add Notification component ([#994](https://github.com/logto-io/logto/issues/994)) ([8530e24](https://github.com/logto-io/logto/commit/8530e249aa6d63efe594a08f800be4bfb43ed77e)) +- **ui:** add social dropdown list for desktop ([#834](https://github.com/logto-io/logto/issues/834)) ([36922b3](https://github.com/logto-io/logto/commit/36922b343f06daa1c7d4125bd0066ec06962123d)) +- **ui:** app notification ([#999](https://github.com/logto-io/logto/issues/999)) ([f4e380f](https://github.com/logto-io/logto/commit/f4e380f0b1b815314b24cec1c9013d9f3bb806a7)) +- **ui:** display error message on social callback page ([#1097](https://github.com/logto-io/logto/issues/1097)) ([f3b8678](https://github.com/logto-io/logto/commit/f3b8678a8c5e938276208c222242c3fedf4d397a)) +- **ui:** implement preview mode ([#852](https://github.com/logto-io/logto/issues/852)) ([ef19fb3](https://github.com/logto-io/logto/commit/ef19fb3d27a84509613b1f1d47819c06e9a6e9d1)) +- **ui:** init destop styling foundation ([#787](https://github.com/logto-io/logto/issues/787)) ([5c02ec3](https://github.com/logto-io/logto/commit/5c02ec3bdae162bd83d26c56f7ae34ee6e505ca2)) +- **ui:** not found page ([#691](https://github.com/logto-io/logto/issues/691)) ([731ff1c](https://github.com/logto-io/logto/commit/731ff1cbdca76104845dcf3d1223953ce8e5af93)) + +### Bug Fixes + +- `lint:report` script ([#730](https://github.com/logto-io/logto/issues/730)) ([3b17324](https://github.com/logto-io/logto/commit/3b17324d189b2fe47985d0bee8b37b4ef1dbdd2b)) +- **console:** socialConnectors in preview data ([#862](https://github.com/logto-io/logto/issues/862)) ([a2cd983](https://github.com/logto-io/logto/commit/a2cd983d97097f86a07f988031b76665958ac24b)) +- revert "chore(deps): update parcel monorepo to v2.6.0" ([877bbc0](https://github.com/logto-io/logto/commit/877bbc0d2c5c0559a3fc9a8e801a13ebff2292a6)) +- **ui:** add body background color ([#831](https://github.com/logto-io/logto/issues/831)) ([be8b862](https://github.com/logto-io/logto/commit/be8b8628ba345bd8f8832b2123a43e70c236406d)) +- **ui:** add default success true for no response api ([#842](https://github.com/logto-io/logto/issues/842)) ([88600c0](https://github.com/logto-io/logto/commit/88600c012c892c969f1e5df7ec5f46874513a058)) +- **ui:** add i18n formater for zh-CN list ([#1009](https://github.com/logto-io/logto/issues/1009)) ([ca5c8aa](https://github.com/logto-io/logto/commit/ca5c8aaec1db7ffc330f50fcdc14400e06ad6f54)) +- **ui:** catch request exceptions with no response body ([#790](https://github.com/logto-io/logto/issues/790)) ([48de9c0](https://github.com/logto-io/logto/commit/48de9c072bb060f3e5aeb785d7a765a66a0912fe)) +- **ui:** fix callback link params for apple ([#985](https://github.com/logto-io/logto/issues/985)) ([362c3a6](https://github.com/logto-io/logto/commit/362c3a6e6ed3cab24a85f9e268509d31430609e4)) +- **ui:** fix ci fail ([#708](https://github.com/logto-io/logto/issues/708)) ([da49812](https://github.com/logto-io/logto/commit/da4981216452ee11cf91c8f52a1d50ef18f9a37f)) +- **UI:** fix connector target and id used in UI ([#838](https://github.com/logto-io/logto/issues/838)) ([cd46505](https://github.com/logto-io/logto/commit/cd4650508f9b1b4d2051e600afdf1e157dcf0631)) +- **ui:** fix count down bug ([#874](https://github.com/logto-io/logto/issues/874)) ([9c1e9ef](https://github.com/logto-io/logto/commit/9c1e9ef7edb39d5d15dcbb21a8789fab78326de5)) +- **ui:** fix create account page reload issue ([#832](https://github.com/logto-io/logto/issues/832)) ([e221758](https://github.com/logto-io/logto/commit/e2217584a40098d6bfcd6a745e8e0d982e8936c0)) +- **ui:** fix drawer overflow bug ([#984](https://github.com/logto-io/logto/issues/984)) ([b9131e9](https://github.com/logto-io/logto/commit/b9131e97659dece341ba4dd0cb47686a24698dcb)) +- **ui:** fix social bug ([#939](https://github.com/logto-io/logto/issues/939)) ([7a17d41](https://github.com/logto-io/logto/commit/7a17d41acf7cc068d0ec5568bcd842db51aa8b39)) +- **ui:** fix social native interaction bug ([#772](https://github.com/logto-io/logto/issues/772)) ([2161856](https://github.com/logto-io/logto/commit/2161856bcd33b66c8390b343cc3591ff284be286)) +- **ui:** fix some of the bug bash issues ([#1053](https://github.com/logto-io/logto/issues/1053)) ([db1b6d2](https://github.com/logto-io/logto/commit/db1b6d247a3d07f81ff1284b1fdbd3e7ffcc97aa)) +- **ui:** fix typo ([#792](https://github.com/logto-io/logto/issues/792)) ([13cd2c1](https://github.com/logto-io/logto/commit/13cd2c100ed32b40da72364d1f4685edd7d6d25a)) +- **ui:** fix ui i18n package error ([#713](https://github.com/logto-io/logto/issues/713)) ([34d798b](https://github.com/logto-io/logto/commit/34d798b645f16aff05b3818797b7914b5d2bc9b3)) +- **ui:** fix undefined dark-primary-color bug ([#876](https://github.com/logto-io/logto/issues/876)) ([542d878](https://github.com/logto-io/logto/commit/542d878231b98710af6e5a8ba6a5a5f74eee73a3)) +- **ui:** hide social signin method if connectors are empty ([#909](https://github.com/logto-io/logto/issues/909)) ([5e0c39e](https://github.com/logto-io/logto/commit/5e0c39e5166072c2c8d729c2e0f714507fd93ba6)) +- **ui:** input fields ([#1125](https://github.com/logto-io/logto/issues/1125)) ([20f7ad9](https://github.com/logto-io/logto/commit/20f7ad986353eb0026cbec417eaed3c334279f86)) +- **ui:** relocate svg jest config ([#856](https://github.com/logto-io/logto/issues/856)) ([d8c62c1](https://github.com/logto-io/logto/commit/d8c62c14a677d9afa8ce4b2c78cdd8fc8b1ee6c1)) +- **ui:** social bind account should back to sign-in page ([#952](https://github.com/logto-io/logto/issues/952)) ([da41369](https://github.com/logto-io/logto/commit/da41369bfd0e444190d33edef6527b32b538dbee)) +- **ui:** ui design review fix ([#697](https://github.com/logto-io/logto/issues/697)) ([15dd1a7](https://github.com/logto-io/logto/commit/15dd1a767e9eddfd37a80b47775afbe327b76d5b)) +- **ui:** ui refinement ([#855](https://github.com/logto-io/logto/issues/855)) ([1661c81](https://github.com/logto-io/logto/commit/1661c8121a9ed1620a4d8fefd51523d2be261089)) +- **ut:** fix ut ([#683](https://github.com/logto-io/logto/issues/683)) ([b0138bd](https://github.com/logto-io/logto/commit/b0138bdc0f2c43f40e20e83b621f3de3d068c171)) diff --git a/packages/experience-legacy/README.md b/packages/experience-legacy/README.md new file mode 100644 index 00000000000..862635fc943 --- /dev/null +++ b/packages/experience-legacy/README.md @@ -0,0 +1,3 @@ +# @logto/experience + +The register and sign-in experience for end-users. diff --git a/packages/experience-legacy/index.html b/packages/experience-legacy/index.html new file mode 100644 index 00000000000..2cdf8212863 --- /dev/null +++ b/packages/experience-legacy/index.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> + <title> + + + + + +
+ + + + diff --git a/packages/experience-legacy/jest.config.ts b/packages/experience-legacy/jest.config.ts new file mode 100644 index 00000000000..e9a92e6ea48 --- /dev/null +++ b/packages/experience-legacy/jest.config.ts @@ -0,0 +1,35 @@ +import type { Config } from '@jest/types'; + +const config: Config.InitialOptions = { + roots: ['/src'], + testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/jest.setup.ts'], + collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}'], + coveragePathIgnorePatterns: ['/node_modules/', '/src/__mocks__/', '/src/include.d/'], + coverageReporters: ['text-summary', 'lcov'], + transform: { + '^.+\\.(t|j)sx?$': [ + '@swc/jest', + { + sourceMaps: true, + jsc: { + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + ], + '\\.(svg)$': 'jest-transformer-svg', + '\\.(png)$': 'jest-transform-stub', + }, + moduleNameMapper: { + '^@/([^?]*)(\\?.*)?$': '/src/$1', + '^@logto/shared/(.*)$': '/../shared/lib/$1', + '\\.module\\.(css|sass|scss)$': 'identity-obj-proxy', + }, + transformIgnorePatterns: ['node_modules/(?!(.*(nanoid|jose|ky|@logto|@silverhand))/)'], +}; + +export default config; diff --git a/packages/experience-legacy/package.json b/packages/experience-legacy/package.json new file mode 100644 index 00000000000..8efd9e88b63 --- /dev/null +++ b/packages/experience-legacy/package.json @@ -0,0 +1,99 @@ +{ + "name": "@logto/experience-legacy", + "version": "1.8.0", + "license": "MPL-2.0", + "type": "module", + "private": true, + "files": [ + "dist" + ], + "scripts": { + "precommit": "lint-staged", + "start": "vite", + "dev": "vite", + "check": "tsc --noEmit", + "build": "vite build", + "lint": "eslint --ext .ts --ext .tsx src", + "lint:report": "pnpm lint --format json --output-file report.json", + "stylelint": "stylelint \"src/**/*.scss\"", + "test:ci": "jest --coverage --silent", + "test": "jest" + }, + "devDependencies": { + "@jest/types": "^29.5.0", + "@logto/connector-kit": "workspace:^4.0.0", + "@logto/core-kit": "workspace:^2.5.0", + "@logto/language-kit": "workspace:^1.1.0", + "@logto/phrases": "workspace:^1.13.0", + "@logto/phrases-experience": "workspace:^1.7.0", + "@logto/schemas": "workspace:^1.19.0", + "@react-spring/shared": "^9.6.1", + "@react-spring/web": "^9.6.1", + "@silverhand/eslint-config": "6.0.1", + "@silverhand/eslint-config-react": "6.0.2", + "@silverhand/essentials": "^2.9.1", + "@silverhand/ts-config": "6.0.0", + "@silverhand/ts-config-react": "6.0.0", + "@simplewebauthn/browser": "^10.0.0", + "@simplewebauthn/types": "^10.0.0", + "@swc/core": "^1.3.52", + "@swc/jest": "^0.2.26", + "@testing-library/react": "^16.0.0", + "@testing-library/react-hooks": "^8.0.1", + "@types/color": "^3.0.3", + "@types/jest": "^29.4.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/react-helmet": "^6.1.6", + "@types/react-modal": "^3.13.1", + "@types/react-router-dom": "^5.3.2", + "@vitejs/plugin-react": "^4.3.1", + "browserslist": "^4.23.2", + "browserslist-to-esbuild": "^2.1.1", + "camelcase-keys": "^9.1.3", + "classnames": "^2.3.1", + "color": "^4.2.3", + "core-js": "^3.34.0", + "eslint": "^8.56.0", + "i18next": "^22.4.15", + "i18next-browser-languagedetector": "^8.0.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-transform-stub": "^2.0.0", + "jest-transformer-svg": "^2.0.0", + "js-base64": "^3.7.5", + "ky": "^1.2.3", + "libphonenumber-js": "^1.10.51", + "lint-staged": "^15.0.0", + "postcss": "^8.4.31", + "postcss-modules": "^4.3.0", + "prettier": "^3.0.0", + "react": "^18.3.1", + "react-device-detect": "^2.2.3", + "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", + "react-hook-form": "^7.53.0", + "react-i18next": "^12.3.1", + "react-modal": "^3.15.1", + "react-router-dom": "^6.10.0", + "react-string-replace": "^1.0.0", + "react-timer-hook": "^3.0.5", + "react-top-loading-bar": "^2.3.1", + "stylelint": "^15.0.0", + "superstruct": "^2.0.0", + "tiny-cookie": "^2.4.1", + "typescript": "^5.5.3", + "use-debounced-loader": "^0.1.1", + "vite": "^5.3.4", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-svgr": "^4.2.0" + }, + "engines": { + "node": "^20.9.0" + }, + "stylelint": { + "extends": "@silverhand/eslint-config-react/.stylelintrc" + }, + "prettier": "@silverhand/eslint-config/.prettierrc" +} diff --git a/packages/experience-legacy/src/App.tsx b/packages/experience-legacy/src/App.tsx new file mode 100644 index 00000000000..3f2912334f8 --- /dev/null +++ b/packages/experience-legacy/src/App.tsx @@ -0,0 +1,168 @@ +import { MfaFactor, experience } from '@logto/schemas'; +import { Route, Routes, BrowserRouter } from 'react-router-dom'; + +import AppLayout from './Layout/AppLayout'; +import AppBoundary from './Providers/AppBoundary'; +import LoadingLayerProvider from './Providers/LoadingLayerProvider'; +import PageContextProvider from './Providers/PageContextProvider'; +import SettingsProvider from './Providers/SettingsProvider'; +import UserInteractionContextProvider from './Providers/UserInteractionContextProvider'; +import Callback from './pages/Callback'; +import Consent from './pages/Consent'; +import Continue from './pages/Continue'; +import DirectSignIn from './pages/DirectSignIn'; +import ErrorPage from './pages/ErrorPage'; +import ForgotPassword from './pages/ForgotPassword'; +import IdentifierRegister from './pages/IdentifierRegister'; +import IdentifierSignIn from './pages/IdentifierSignIn'; +import MfaBinding from './pages/MfaBinding'; +import BackupCodeBinding from './pages/MfaBinding/BackupCodeBinding'; +import TotpBinding from './pages/MfaBinding/TotpBinding'; +import WebAuthnBinding from './pages/MfaBinding/WebAuthnBinding'; +import MfaVerification from './pages/MfaVerification'; +import BackupCodeVerification from './pages/MfaVerification/BackupCodeVerification'; +import TotpVerification from './pages/MfaVerification/TotpVerification'; +import WebAuthnVerification from './pages/MfaVerification/WebAuthnVerification'; +import Register from './pages/Register'; +import RegisterPassword from './pages/RegisterPassword'; +import ResetPassword from './pages/ResetPassword'; +import ResetPasswordLanding from './pages/ResetPasswordLanding'; +import SignIn from './pages/SignIn'; +import SignInPassword from './pages/SignInPassword'; +import SingleSignOnConnectors from './pages/SingleSignOnConnectors'; +import SingleSignOnEmail from './pages/SingleSignOnEmail'; +import SingleSignOnLanding from './pages/SingleSignOnLanding'; +import SocialLanding from './pages/SocialLanding'; +import SocialLinkAccount from './pages/SocialLinkAccount'; +import SocialSignInWebCallback from './pages/SocialSignInWebCallback'; +import Springboard from './pages/Springboard'; +import VerificationCode from './pages/VerificationCode'; +import { UserMfaFlow } from './types'; +import { handleSearchParametersData } from './utils/search-parameters'; + +import './scss/normalized.scss'; + +handleSearchParametersData(); + +const App = () => { + return ( + + + + + + + }> + } /> + } /> + } + /> + } /> + + }> + } + /> + + {/* Sign-in */} + + } /> + } /> + + + {/* Register */} + + } /> + } /> + + + {/* Forgot password */} + + } /> + } /> + + + {/* Passwordless verification code */} + } /> + + {/* Mfa binding */} + + } /> + } /> + } /> + } /> + + + {/* Mfa verification */} + + } /> + } /> + } /> + } /> + + + {/* Continue set up missing profile */} + + } /> + + + {/* Social sign-in pages */} + + } /> + } /> + + + {/* Single sign-on */} + + {/* Single sign-on first screen landing page */} + } /> + } /> + } /> + + + {/* Consent */} + } /> + + {/* + * Identifier sign-in (first screen) + * The first screen which only display specific identifier-based sign-in methods to users + */} + } + /> + + {/* + * Identifier register (first screen) + * The first screen which only display specific identifier-based registration methods to users + */} + } + /> + + {/* + * Reset password (first screen) + * The first screen which allow users to directly access the password reset page + */} + } + /> + + } /> + + + + + + + + + ); +}; + +export default App; diff --git a/packages/experience-legacy/src/Layout/AppLayout/CustomContent.tsx b/packages/experience-legacy/src/Layout/AppLayout/CustomContent.tsx new file mode 100644 index 00000000000..06699347dda --- /dev/null +++ b/packages/experience-legacy/src/Layout/AppLayout/CustomContent.tsx @@ -0,0 +1,28 @@ +import { useLocation } from 'react-router-dom'; + +import { useSieMethods } from '@/hooks/use-sie'; + +type Props = { + readonly className?: string; +}; + +const CustomContent = ({ className }: Props) => { + const { customContent } = useSieMethods(); + const { pathname } = useLocation(); + + const customHtml = customContent?.[pathname]; + + if (!customHtml) { + return null; + } + + try { + // Expected error; CustomContent content is load from Logto remote server + // eslint-disable-next-line react/no-danger + return
; + } catch { + return null; + } +}; + +export default CustomContent; diff --git a/packages/experience-legacy/src/Layout/AppLayout/index.module.scss b/packages/experience-legacy/src/Layout/AppLayout/index.module.scss new file mode 100644 index 00000000000..b1209c7348f --- /dev/null +++ b/packages/experience-legacy/src/Layout/AppLayout/index.module.scss @@ -0,0 +1,66 @@ +@use '@/scss/underscore' as _; + +/* Main Layout */ +.viewBox { + position: absolute; + inset: 0; + overflow: auto; +} + +.container { + min-height: 100%; + @include _.flex_column(center, center); +} + +.main { + @include _.flex_column; +} + +:global(body.mobile) { + .container { + padding-bottom: env(safe-area-inset-bottom); + } + + .main { + flex: 1; + align-self: stretch; + padding: _.unit(4) _.unit(5); + position: relative; + background: var(--color-bg-body); + } + + .signature { + margin: _.unit(10) 0 _.unit(2); + } +} + +:global(body.desktop) { + .container { + padding: _.unit(5); + } + + .main { + width: 540px; + min-height: 540px; + position: relative; + padding: _.unit(6); + border-radius: 16px; + background: var(--color-bg-float); + box-shadow: var(--color-shadow-2); + } + + .signature { + position: absolute; + bottom: 0; + transform: translateY(calc(100% + _.unit(7))); + // Have to use padding instead of margin. Overflow margin spacing will be ignored by the browser. + padding-bottom: _.unit(7); + } + + @media only screen and (max-width: 580px) { + .main { + align-self: stretch; + width: auto; + } + } +} diff --git a/packages/experience-legacy/src/Layout/AppLayout/index.tsx b/packages/experience-legacy/src/Layout/AppLayout/index.tsx new file mode 100644 index 00000000000..1b0ce7beb26 --- /dev/null +++ b/packages/experience-legacy/src/Layout/AppLayout/index.tsx @@ -0,0 +1,29 @@ +import classNames from 'classnames'; +import { Outlet } from 'react-router-dom'; + +import LogtoSignature from '@/components/LogtoSignature'; +import DevelopmentTenantNotification from '@/containers/DevelopmentTenantNotification'; +import usePlatform from '@/hooks/use-platform'; +import { layoutClassNames } from '@/utils/consts'; + +import CustomContent from './CustomContent'; +import styles from './index.module.scss'; + +const AppLayout = () => { + const { isMobile } = usePlatform(); + + return ( +
+
+ {!isMobile && } +
+ + + +
+
+
+ ); +}; + +export default AppLayout; diff --git a/packages/experience-legacy/src/Layout/FirstScreenLayout/index.module.scss b/packages/experience-legacy/src/Layout/FirstScreenLayout/index.module.scss new file mode 100644 index 00000000000..18ee6754532 --- /dev/null +++ b/packages/experience-legacy/src/Layout/FirstScreenLayout/index.module.scss @@ -0,0 +1,25 @@ +@use '@/scss/underscore' as _; + +.wrapper { + @include _.full-page; + @include _.flex-column(normal, normal); + @include _.full-width; + + > *:last-child { + margin-bottom: 0; + } +} + +:global(body.desktop) { + .wrapper { + padding: _.unit(6) 0; + } + + .placeholderTop { + flex: 3; + } + + .placeholderBottom { + flex: 5; + } +} diff --git a/packages/experience-legacy/src/Layout/FirstScreenLayout/index.tsx b/packages/experience-legacy/src/Layout/FirstScreenLayout/index.tsx new file mode 100644 index 00000000000..8aaa76165f2 --- /dev/null +++ b/packages/experience-legacy/src/Layout/FirstScreenLayout/index.tsx @@ -0,0 +1,28 @@ +import { type ReactNode, useContext } from 'react'; + +import PageContext from '@/Providers/PageContextProvider/PageContext'; + +import PageMeta from '../../components/PageMeta'; +import type { Props as PageMetaProps } from '../../components/PageMeta'; + +import styles from './index.module.scss'; + +type Props = { + readonly children: ReactNode; + readonly pageMeta: PageMetaProps; +}; + +const FirstScreenLayout = ({ children, pageMeta }: Props) => { + const { platform } = useContext(PageContext); + + return ( + <> + + {platform === 'web' &&
} +
{children}
+ {platform === 'web' &&
} + + ); +}; + +export default FirstScreenLayout; diff --git a/packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.module.scss b/packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.module.scss new file mode 100644 index 00000000000..bf8b5680d9f --- /dev/null +++ b/packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.module.scss @@ -0,0 +1,33 @@ +@use '@/scss/underscore' as _; + +.header { + margin: _.unit(6) 0; +} + +.description { + margin-top: _.unit(2); + @include _.text-hint; +} + +.terms { + margin-top: _.unit(4); + @include _.text-hint; + text-align: center; + font: var(--font-body-3); +} + +.link { + margin-top: _.unit(7); +} + +:global(body.mobile) { + .title { + @include _.title; + } +} + +:global(body.desktop) { + .title { + @include _.title_desktop; + } +} diff --git a/packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.tsx b/packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.tsx new file mode 100644 index 00000000000..b7c160a4cbe --- /dev/null +++ b/packages/experience-legacy/src/Layout/FocusedAuthPageLayout/index.tsx @@ -0,0 +1,61 @@ +import { type AgreeToTermsPolicy } from '@logto/schemas'; +import { type TFuncKey } from 'i18next'; +import { useMemo, type ReactNode } from 'react'; + +import DynamicT from '@/components/DynamicT'; +import type { Props as PageMetaProps } from '@/components/PageMeta'; +import type { Props as TextLinkProps } from '@/components/TextLink'; +import TextLink from '@/components/TextLink'; +import TermsAndPrivacyLinks from '@/containers/TermsAndPrivacyLinks'; +import useTerms from '@/hooks/use-terms'; + +import FirstScreenLayout from '../FirstScreenLayout'; + +import styles from './index.module.scss'; + +type Props = { + readonly children: ReactNode; + readonly pageMeta: PageMetaProps; + readonly title: TFuncKey; + readonly description: string; + readonly footerTermsDisplayPolicies?: AgreeToTermsPolicy[]; + readonly authOptionsLink: TextLinkProps; +}; + +/** + * FocusedAuthPageLayout Component + * + * This layout component is designed for focused authentication pages that serve as the first screen + * for specific auth methods, such as identifier sign-in, identifier-register, and single sign-on landing pages. + */ +const FocusedAuthPageLayout = ({ + children, + pageMeta, + title, + description, + footerTermsDisplayPolicies = [], + authOptionsLink, +}: Props) => { + const { agreeToTermsPolicy } = useTerms(); + + const shouldDisplayFooterTerms = useMemo( + () => agreeToTermsPolicy && footerTermsDisplayPolicies.includes(agreeToTermsPolicy), + [agreeToTermsPolicy, footerTermsDisplayPolicies] + ); + + return ( + +
+
+ +
+
{description}
+
+ {children} + {shouldDisplayFooterTerms && } + +
+ ); +}; + +export default FocusedAuthPageLayout; diff --git a/packages/experience-legacy/src/Layout/LandingPageLayout/index.module.scss b/packages/experience-legacy/src/Layout/LandingPageLayout/index.module.scss new file mode 100644 index 00000000000..a11c10a54f0 --- /dev/null +++ b/packages/experience-legacy/src/Layout/LandingPageLayout/index.module.scss @@ -0,0 +1,36 @@ +@use '@/scss/underscore' as _; + +.wrapper { + @include _.full-page; + @include _.flex-column(normal, normal); + @include _.full-width; + + > *:last-child { + margin-bottom: 0; + } +} + +:global(body.mobile) { + .header { + margin-top: _.unit(3); + padding-bottom: _.unit(7); + } +} + +:global(body.desktop) { + .wrapper { + padding: _.unit(6) 0; + } + + .header { + margin-bottom: _.unit(6); + } + + .placeholderTop { + flex: 3; + } + + .placeholderBottom { + flex: 5; + } +} diff --git a/packages/experience-legacy/src/Layout/LandingPageLayout/index.tsx b/packages/experience-legacy/src/Layout/LandingPageLayout/index.tsx new file mode 100644 index 00000000000..dc40e0006a4 --- /dev/null +++ b/packages/experience-legacy/src/Layout/LandingPageLayout/index.tsx @@ -0,0 +1,54 @@ +import { type ConsentInfoResponse } from '@logto/schemas'; +import classNames from 'classnames'; +import type { TFuncKey } from 'i18next'; +import type { ReactNode } from 'react'; +import { useContext } from 'react'; + +import PageContext from '@/Providers/PageContextProvider/PageContext'; +import BrandingHeader from '@/components/BrandingHeader'; +import { layoutClassNames } from '@/utils/consts'; +import { getBrandingLogoUrl } from '@/utils/logo'; + +import FirstScreenLayout from '../FirstScreenLayout'; + +import styles from './index.module.scss'; + +type ThirdPartyBranding = ConsentInfoResponse['application']['branding']; + +type Props = { + readonly children: ReactNode; + readonly title: TFuncKey; + readonly titleInterpolation?: Record; + readonly thirdPartyBranding?: ThirdPartyBranding; +}; + +const LandingPageLayout = ({ children, title, titleInterpolation, thirdPartyBranding }: Props) => { + const { experienceSettings, theme } = useContext(PageContext); + + if (!experienceSettings) { + return null; + } + + const { + color: { isDarkModeEnabled }, + branding, + } = experienceSettings; + + return ( + + + {children} + + ); +}; + +export default LandingPageLayout; diff --git a/packages/experience-legacy/src/Layout/SecondaryPageLayout/index.module.scss b/packages/experience-legacy/src/Layout/SecondaryPageLayout/index.module.scss new file mode 100644 index 00000000000..e2e8f791d7f --- /dev/null +++ b/packages/experience-legacy/src/Layout/SecondaryPageLayout/index.module.scss @@ -0,0 +1,53 @@ +@use '@/scss/underscore' as _; + +.wrapper { + @include _.full-page; +} + +.container { + @include _.full-width; + margin-top: _.unit(2); +} + +.header { + margin-bottom: _.unit(6); +} + +.description { + margin-top: _.unit(2); + @include _.text-hint; +} + +:global(body.mobile) { + .container { + margin-top: _.unit(2); + } + + .title { + @include _.title; + } + + .notification { + margin: 0 _.unit(-5) _.unit(6); + } +} + +:global(body.desktop) { + .container { + margin-top: _.unit(12); + } + + .header { + margin-bottom: _.unit(4); + } + + .title { + @include _.title_desktop; + } + + .notification { + @include _.full-width; + margin-top: _.unit(6); + border-radius: var(--radius); + } +} diff --git a/packages/experience-legacy/src/Layout/SecondaryPageLayout/index.tsx b/packages/experience-legacy/src/Layout/SecondaryPageLayout/index.tsx new file mode 100644 index 00000000000..95cdb2f54f6 --- /dev/null +++ b/packages/experience-legacy/src/Layout/SecondaryPageLayout/index.tsx @@ -0,0 +1,67 @@ +import type { TFuncKey } from 'i18next'; +import { type ReactElement } from 'react'; + +import DynamicT from '@/components/DynamicT'; +import NavBar from '@/components/NavBar'; +import PageMeta from '@/components/PageMeta'; +import usePlatform from '@/hooks/use-platform'; + +import { InlineNotification } from '../../components/Notification'; + +import styles from './index.module.scss'; + +type Props = { + readonly title: TFuncKey; + readonly description?: TFuncKey | ReactElement | ''; + readonly titleProps?: Record; + readonly descriptionProps?: Record; + readonly notification?: TFuncKey; + readonly onSkip?: () => void; + readonly isNavBarHidden?: boolean; + readonly children: React.ReactNode; +}; + +const SecondaryPageLayout = ({ + title, + description, + titleProps, + descriptionProps, + notification, + onSkip, + isNavBarHidden, + children, +}: Props) => { + const { isMobile } = usePlatform(); + + return ( +
+ + + {isMobile && notification && ( + + )} +
+
+
+ +
+ {description && ( +
+ {typeof description === 'string' ? ( + + ) : ( + description + )} +
+ )} +
+ {children} +
+ {!isMobile && notification && ( + + )} +
+ ); +}; + +export default SecondaryPageLayout; diff --git a/packages/experience-legacy/src/Layout/SectionLayout/index.module.scss b/packages/experience-legacy/src/Layout/SectionLayout/index.module.scss new file mode 100644 index 00000000000..704c6109013 --- /dev/null +++ b/packages/experience-legacy/src/Layout/SectionLayout/index.module.scss @@ -0,0 +1,11 @@ +@use '@/scss/underscore' as _; + +.title { + font: var(--font-title-3); +} + +.description { + font: var(--font-body-2); + color: var(--color-type-secondary); + margin-top: _.unit(1); +} diff --git a/packages/experience-legacy/src/Layout/SectionLayout/index.tsx b/packages/experience-legacy/src/Layout/SectionLayout/index.tsx new file mode 100644 index 00000000000..2ad4d101a03 --- /dev/null +++ b/packages/experience-legacy/src/Layout/SectionLayout/index.tsx @@ -0,0 +1,30 @@ +import { type TFuncKey } from 'i18next'; +import { type ReactNode } from 'react'; + +import DynamicT from '@/components/DynamicT'; + +import styles from './index.module.scss'; + +type Props = { + readonly title: TFuncKey; + readonly description: TFuncKey; + readonly titleProps?: Record; + readonly descriptionProps?: Record; + readonly children: ReactNode; +}; + +const SectionLayout = ({ title, description, titleProps, descriptionProps, children }: Props) => { + return ( +
+
+ +
+
+ +
+ {children} +
+ ); +}; + +export default SectionLayout; diff --git a/packages/experience-legacy/src/Layout/StaticPageLayout/index.module.scss b/packages/experience-legacy/src/Layout/StaticPageLayout/index.module.scss new file mode 100644 index 00000000000..27eea52374d --- /dev/null +++ b/packages/experience-legacy/src/Layout/StaticPageLayout/index.module.scss @@ -0,0 +1,7 @@ +@use '@/scss/underscore' as _; + + +.wrapper { + @include _.full-page; + @include _.flex-column; +} diff --git a/packages/experience-legacy/src/Layout/StaticPageLayout/index.tsx b/packages/experience-legacy/src/Layout/StaticPageLayout/index.tsx new file mode 100644 index 00000000000..8204e4df2eb --- /dev/null +++ b/packages/experience-legacy/src/Layout/StaticPageLayout/index.tsx @@ -0,0 +1,11 @@ +import styles from './index.module.scss'; + +type Props = { + readonly children: React.ReactNode; +}; + +const StaticPageLayout = ({ children }: Props) => { + return
{children}
; +}; + +export default StaticPageLayout; diff --git a/packages/experience-legacy/src/Providers/AppBoundary/AppMeta.tsx b/packages/experience-legacy/src/Providers/AppBoundary/AppMeta.tsx new file mode 100644 index 00000000000..5b1d0403f87 --- /dev/null +++ b/packages/experience-legacy/src/Providers/AppBoundary/AppMeta.tsx @@ -0,0 +1,56 @@ +import { Theme } from '@logto/schemas'; +import { conditionalString } from '@silverhand/essentials'; +import classNames from 'classnames'; +import i18next from 'i18next'; +import { useContext } from 'react'; +import { Helmet } from 'react-helmet'; + +import PageContext from '@/Providers/PageContextProvider/PageContext'; +import defaultAppleTouchLogo from '@/assets/apple-touch-icon.png'; +import defaultFavicon from '@/assets/favicon.png'; +import { type SignInExperienceResponse } from '@/types'; + +import styles from './index.module.scss'; + +const themeToFavicon = Object.freeze({ + [Theme.Light]: 'favicon', + [Theme.Dark]: 'darkFavicon', +} as const satisfies Record); + +/** + * User React Helmet to manage html and body attributes + * @see https://github.com/nfl/react-helmet + * + * - lang: set html lang attribute + * - data-theme: set html data-theme attribute + * - favicon: set favicon + * - apple-touch-icon: set apple touch icon + * - body class: set preview body class + * - body class: set platform body class + * - body class: set theme body class + * - custom css: set custom css style tag + */ + +const AppMeta = () => { + const { experienceSettings, theme, platform, isPreview } = useContext(PageContext); + const favicon = + experienceSettings?.branding[themeToFavicon[theme]] ?? experienceSettings?.branding.favicon; + + return ( + + + + + {experienceSettings?.customCss && } + + + ); +}; + +export default AppMeta; diff --git a/packages/experience-legacy/src/Providers/AppBoundary/index.module.scss b/packages/experience-legacy/src/Providers/AppBoundary/index.module.scss new file mode 100644 index 00000000000..29f78b78e11 --- /dev/null +++ b/packages/experience-legacy/src/Providers/AppBoundary/index.module.scss @@ -0,0 +1,37 @@ +@use '@/scss/colors' as colors; +@use '@/scss/underscore' as _; + +body { + &.light { + @include colors.light; + } + + &.dark { + @include colors.dark; + } +} + +/* Preview Settings */ +.preview { + pointer-events: none; + user-select: none; + + .viewBox::-webkit-scrollbar { + display: none; + } + + main { + pointer-events: none; + user-select: none; + } +} + +:global(body.mobile) { + --max-width: 360px; + background: var(--color-bg-body); +} + +:global(body.desktop) { + --max-width: 400px; + background: var(--color-bg-float-base); +} diff --git a/packages/experience-legacy/src/Providers/AppBoundary/index.tsx b/packages/experience-legacy/src/Providers/AppBoundary/index.tsx new file mode 100644 index 00000000000..57cea55a324 --- /dev/null +++ b/packages/experience-legacy/src/Providers/AppBoundary/index.tsx @@ -0,0 +1,30 @@ +import type { ReactElement } from 'react'; + +import useColorTheme from '@/Providers/AppBoundary/use-color-theme'; + +import ConfirmModalProvider from '../ConfirmModalProvider'; +import IframeModalProvider from '../IframeModalProvider'; +import ToastProvider from '../ToastProvider'; + +import AppMeta from './AppMeta'; + +type Props = { + readonly children: ReactElement; +}; + +const AppBoundary = ({ children }: Props) => { + useColorTheme(); + + return ( + <> + + + + {children} + + + + ); +}; + +export default AppBoundary; diff --git a/packages/experience-legacy/src/Providers/AppBoundary/use-color-theme.ts b/packages/experience-legacy/src/Providers/AppBoundary/use-color-theme.ts new file mode 100644 index 00000000000..20c2e754f1d --- /dev/null +++ b/packages/experience-legacy/src/Providers/AppBoundary/use-color-theme.ts @@ -0,0 +1,62 @@ +import { absoluteDarken, absoluteLighten } from '@logto/core-kit'; +import { Theme } from '@logto/schemas'; +import color from 'color'; +import { useEffect, useContext } from 'react'; + +import PageContext from '@/Providers/PageContextProvider/PageContext'; + +const generateLightColorLibrary = (primaryColor: color) => ({ + [`--color-brand-default`]: primaryColor.hex(), + [`--color-brand-hover`]: absoluteLighten(primaryColor, 10).string(), + [`--color-brand-pressed`]: absoluteDarken(primaryColor, 10).string(), + [`--color-brand-loading`]: absoluteLighten(primaryColor, 15).string(), + [`--color-overlay-brand-focused`]: primaryColor.alpha(0.16).string(), + [`--color-overlay-brand-hover`]: primaryColor.alpha(0.08).string(), + [`--color-overlay-brand-pressed`]: primaryColor.alpha(0.12).string(), +}); + +const generateDarkColorLibrary = (primaryColor: color) => ({ + [`--color-brand-default`]: primaryColor.hex(), + [`--color-brand-hover`]: absoluteLighten(primaryColor, 10).string(), + [`--color-brand-pressed`]: absoluteDarken(primaryColor, 10).string(), + [`--color-brand-loading`]: absoluteDarken(primaryColor, 10).string(), + [`--color-overlay-brand-focused`]: absoluteLighten(primaryColor, 30).rgb().alpha(0.16).string(), + [`--color-overlay-brand-hover`]: absoluteLighten(primaryColor, 30).rgb().alpha(0.08).string(), + [`--color-overlay-brand-pressed`]: absoluteLighten(primaryColor, 30).rgb().alpha(0.12).string(), +}); + +const useColorTheme = () => { + const { theme, experienceSettings } = useContext(PageContext); + const primaryColor = experienceSettings?.color.primaryColor; + const darkPrimaryColor = experienceSettings?.color.darkPrimaryColor; + + useEffect(() => { + if (!primaryColor) { + return; + } + + const lightPrimary = color(primaryColor); + + if (theme === Theme.Light) { + const lightColorLibrary = generateLightColorLibrary(lightPrimary); + + for (const [key, value] of Object.entries(lightColorLibrary)) { + document.body.style.setProperty(key, value); + } + + return; + } + + const darkPrimary = darkPrimaryColor + ? color(darkPrimaryColor) + : absoluteLighten(lightPrimary, 10); + + const darkColorLibrary = generateDarkColorLibrary(darkPrimary); + + for (const [key, value] of Object.entries(darkColorLibrary)) { + document.body.style.setProperty(key, value); + } + }, [darkPrimaryColor, primaryColor, theme]); +}; + +export default useColorTheme; diff --git a/packages/experience-legacy/src/Providers/ConfirmModalProvider/index.tsx b/packages/experience-legacy/src/Providers/ConfirmModalProvider/index.tsx new file mode 100644 index 00000000000..5836a5c87a8 --- /dev/null +++ b/packages/experience-legacy/src/Providers/ConfirmModalProvider/index.tsx @@ -0,0 +1,161 @@ +import type { Nullable } from '@silverhand/essentials'; +import { noop } from '@silverhand/essentials'; +import { useState, useRef, useMemo, createContext, useCallback } from 'react'; + +import type { ModalProps } from '@/components/ConfirmModal'; +import { WebModal, MobileModal } from '@/components/ConfirmModal'; +import usePlatform from '@/hooks/use-platform'; + +type ConfirmModalType = 'alert' | 'confirm'; + +type ConfirmModalState = Omit & { + ModalContent: string | (() => Nullable); + type: ConfirmModalType; + isConfirmLoading?: boolean; + isCancelLoading?: boolean; +}; + +/** + * Props for promise-based modal usage + */ +type PromiseConfirmModalProps = Omit & { + type?: ConfirmModalType; +}; + +/** + * Props for callback-based modal usage + */ +export type CallbackConfirmModalProps = PromiseConfirmModalProps & { + onConfirm?: () => Promise | void; + onCancel?: () => Promise | void; +}; + +type ConfirmModalContextType = { + showPromise: (props: PromiseConfirmModalProps) => Promise<[boolean, unknown?]>; + showCallback: (props: CallbackConfirmModalProps) => void; +}; + +export const ConfirmModalContext = createContext({ + showPromise: async () => [true], + showCallback: noop, +}); + +type Props = { + readonly children?: React.ReactNode; +}; + +const defaultModalState: ConfirmModalState = { + isOpen: false, + type: 'confirm', + ModalContent: () => null, + isConfirmLoading: false, + isCancelLoading: false, +}; + +/** + * ConfirmModalProvider component + * + * This component provides a context for managing confirm modals throughout the application. + * It supports both promise-based and callback-based usage patterns. see `usePromiseConfirmModal` and `useConfirmModal` hooks. + */ +const ConfirmModalProvider = ({ children }: Props) => { + const [modalState, setModalState] = useState(defaultModalState); + + const resolver = useRef<(value: [result: boolean, data?: unknown]) => void>(); + const callbackRef = useRef<{ + onConfirm?: () => Promise | void; + onCancel?: () => Promise | void; + }>({}); + + const { isMobile } = usePlatform(); + + const ConfirmModal = isMobile ? MobileModal : WebModal; + + const handleShowPromise = useCallback( + async ({ type = 'confirm', ...props }: PromiseConfirmModalProps) => { + resolver.current?.([false]); + + setModalState({ + isOpen: true, + type, + isConfirmLoading: false, + isCancelLoading: false, + ...props, + }); + + return new Promise<[result: boolean, data?: unknown]>((resolve) => { + // eslint-disable-next-line @silverhand/fp/no-mutation + resolver.current = resolve; + }); + }, + [] + ); + + const handleShowCallback = useCallback( + ({ type = 'confirm', onConfirm, onCancel, ...props }: CallbackConfirmModalProps) => { + resolver.current?.([false]); + + setModalState({ + isOpen: true, + type, + isConfirmLoading: false, + ...props, + }); + + // eslint-disable-next-line @silverhand/fp/no-mutation + callbackRef.current = { onConfirm, onCancel }; + }, + [] + ); + + const handleConfirm = useCallback(async (data?: unknown) => { + if (callbackRef.current.onConfirm) { + setModalState((previous) => ({ ...previous, isConfirmLoading: true })); + await callbackRef.current.onConfirm(); + } + resolver.current?.([true, data]); + setModalState(defaultModalState); + }, []); + + const handleCancel = useCallback(async (data?: unknown) => { + if (callbackRef.current.onCancel) { + setModalState((previous) => ({ ...previous, isCancelLoading: true })); + await callbackRef.current.onCancel(); + } + resolver.current?.([false, data]); + setModalState(defaultModalState); + }, []); + + const contextValue = useMemo( + () => ({ + showPromise: handleShowPromise, + showCallback: handleShowCallback, + }), + [handleShowPromise, handleShowCallback] + ); + + const { ModalContent, type, ...restProps } = modalState; + + return ( + + {children} + { + void handleConfirm(); + } + : undefined + } + onClose={() => { + void handleCancel(); + }} + > + {typeof ModalContent === 'string' ? ModalContent : } + + + ); +}; + +export default ConfirmModalProvider; diff --git a/packages/experience-legacy/src/Providers/ConfirmModalProvider/indext.test.tsx b/packages/experience-legacy/src/Providers/ConfirmModalProvider/indext.test.tsx new file mode 100644 index 00000000000..acb80bd8064 --- /dev/null +++ b/packages/experience-legacy/src/Providers/ConfirmModalProvider/indext.test.tsx @@ -0,0 +1,203 @@ +import { render, fireEvent, waitFor } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; + +import { useConfirmModal, usePromiseConfirmModal } from '@/hooks/use-confirm-modal'; + +import ConfirmModalProvider from '.'; + +const confirmHandler = jest.fn(); +const cancelHandler = jest.fn(); + +const PromiseConfirmModalTestComponent = () => { + const { show } = usePromiseConfirmModal(); + + const onClick = async () => { + const [result] = await show({ ModalContent: 'confirm modal content' }); + + if (result) { + confirmHandler(); + + return; + } + + cancelHandler(); + }; + + return ; +}; + +const CallbackConfirmModalTestComponent = () => { + const { show } = useConfirmModal(); + + const onClick = () => { + show({ + ModalContent: 'confirm modal content', + onConfirm: confirmHandler, + onCancel: cancelHandler, + }); + }; + + return ; +}; + +describe('confirm modal provider', () => { + describe('promise confirm modal', () => { + it('render confirm modal', async () => { + const { queryByText, getByText } = render( + + + + ); + + const trigger = getByText('show modal'); + + act(() => { + fireEvent.click(trigger); + }); + + await waitFor(() => { + expect(queryByText('confirm modal content')).not.toBeNull(); + expect(queryByText('action.confirm')).not.toBeNull(); + expect(queryByText('action.cancel')).not.toBeNull(); + }); + }); + + it('confirm callback of confirm modal', async () => { + const { queryByText, getByText } = render( + + + + ); + + const trigger = getByText('show modal'); + + act(() => { + fireEvent.click(trigger); + }); + + await waitFor(() => { + expect(queryByText('confirm modal content')).not.toBeNull(); + expect(queryByText('action.confirm')).not.toBeNull(); + }); + + const confirm = getByText('action.confirm'); + + act(() => { + fireEvent.click(confirm); + }); + + await waitFor(() => { + expect(confirmHandler).toBeCalled(); + }); + }); + + it('cancel callback of confirm modal', async () => { + const { queryByText, getByText } = render( + + + + ); + + const trigger = getByText('show modal'); + + act(() => { + fireEvent.click(trigger); + }); + + await waitFor(() => { + expect(queryByText('confirm modal content')).not.toBeNull(); + expect(queryByText('action.cancel')).not.toBeNull(); + }); + + const cancel = getByText('action.cancel'); + + act(() => { + fireEvent.click(cancel); + }); + + await waitFor(() => { + expect(cancelHandler).toBeCalled(); + }); + }); + }); + + describe('callback confirm modal', () => { + it('render confirm modal', async () => { + const { queryByText, getByText } = render( + + + + ); + + const trigger = getByText('show modal'); + + act(() => { + fireEvent.click(trigger); + }); + + await waitFor(() => { + expect(queryByText('confirm modal content')).not.toBeNull(); + expect(queryByText('action.confirm')).not.toBeNull(); + expect(queryByText('action.cancel')).not.toBeNull(); + }); + }); + + it('confirm callback of confirm modal', async () => { + const { queryByText, getByText } = render( + + + + ); + + const trigger = getByText('show modal'); + + act(() => { + fireEvent.click(trigger); + }); + + await waitFor(() => { + expect(queryByText('confirm modal content')).not.toBeNull(); + expect(queryByText('action.confirm')).not.toBeNull(); + }); + + const confirm = getByText('action.confirm'); + + act(() => { + fireEvent.click(confirm); + }); + + await waitFor(() => { + expect(confirmHandler).toBeCalled(); + }); + }); + + it('cancel callback of confirm modal', async () => { + const { queryByText, getByText } = render( + + + + ); + + const trigger = getByText('show modal'); + + act(() => { + fireEvent.click(trigger); + }); + + await waitFor(() => { + expect(queryByText('confirm modal content')).not.toBeNull(); + expect(queryByText('action.cancel')).not.toBeNull(); + }); + + const cancel = getByText('action.cancel'); + + act(() => { + fireEvent.click(cancel); + }); + + await waitFor(() => { + expect(cancelHandler).toBeCalled(); + }); + }); + }); +}); diff --git a/packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.module.scss b/packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.module.scss new file mode 100644 index 00000000000..643d69bb2e8 --- /dev/null +++ b/packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.module.scss @@ -0,0 +1,71 @@ +@use '@/scss/underscore' as _; + + +.overlay { + z-index: 101; +} + +.modal { + z-index: 101; + position: absolute; + inset: 0; + overflow: auto; +} + +.container { + background: var(--color-bg-body); + height: 100%; + @include _.flex-column; + align-items: stretch; + overflow: hidden; +} + +.modal, +.container { + &:focus-visible { + outline: none; + } +} + +.header { + padding: _.unit(2) _.unit(5); +} + +.content { + flex: 1; + width: 100%; +} + +.iframe { + width: 100%; + height: 100%; + border: none; + background: var(--color-bg-body); + opacity: 0%; + transition: opacity 0.2s ease-in-out; + + &.loaded { + opacity: 100%; + } +} + +.loader { + background: var(--color-brand-default); +} + +/* stylelint-disable selector-class-pattern */ +:global { + .ReactModal__Content[id='iframe-modal'] { + transform: translateY(100%); + transition: transform 0.3s ease-in-out; + } + + .ReactModal__Content--after-open[id='iframe-modal'] { + transform: translateY(0); + } + + .ReactModal__Content--before-close[id='iframe-modal'] { + transform: translateY(100%); + } +} +/* stylelint-enable selector-class-pattern */ diff --git a/packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.tsx b/packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.tsx new file mode 100644 index 00000000000..0db5d5866ea --- /dev/null +++ b/packages/experience-legacy/src/Providers/IframeModalProvider/IframeModal/index.tsx @@ -0,0 +1,71 @@ +import classNames from 'classnames'; +import { useRef, useState } from 'react'; +import ReactModal from 'react-modal'; +import type { LoadingBarRef } from 'react-top-loading-bar'; +import LoadingBar from 'react-top-loading-bar'; + +import NavBar from '@/components/NavBar'; + +import styles from './index.module.scss'; + +type ModalProps = { + readonly className?: string; + readonly title?: string; + readonly href?: string; + readonly onClose: () => void; +}; + +const IframeModal = ({ className, title = '', href = '', onClose }: ModalProps) => { + const [isLoaded, setIsLoaded] = useState(false); + const loadingBarRef = useRef(null); + + const brandingColor = document.body.style.getPropertyValue('--color-brand-default') || '#5d34f2'; + + return ( + { + loadingBarRef.current?.continuousStart(); + }} + onRequestClose={onClose} + > +
+
+ +
+ +
+