diff --git a/packages/experience/src/App.tsx b/packages/experience/src/App.tsx index 56e03faa5..3ab55e763 100644 --- a/packages/experience/src/App.tsx +++ b/packages/experience/src/App.tsx @@ -14,6 +14,7 @@ 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'; @@ -129,6 +130,12 @@ const App = () => { path={experience.routes.identifierSignIn} element={} /> + + {/* Identifier register */} + } + /> )} diff --git a/packages/experience/src/pages/IdentifierRegister/index.tsx b/packages/experience/src/pages/IdentifierRegister/index.tsx new file mode 100644 index 000000000..a7ab58c66 --- /dev/null +++ b/packages/experience/src/pages/IdentifierRegister/index.tsx @@ -0,0 +1,41 @@ +import { AgreeToTermsPolicy, experience } from '@logto/schemas'; +import { useTranslation } from 'react-i18next'; +import { Navigate } from 'react-router-dom'; + +import IdentifierPageLayout from '@/Layout/IdentifierPageLayout'; +import { identifierInputDescriptionMap } from '@/utils/form'; + +import IdentifierRegisterForm from '../Register/IdentifierRegisterForm'; + +import useIdentifierSignUpMethods from './use-identifier-sign-up-methods'; + +const IdentifierRegister = () => { + const { t } = useTranslation(); + const signUpMethods = useIdentifierSignUpMethods(); + + /** + * Fallback to sign-in page if no sign up methods are available (not allowed to create an account). + */ + if (signUpMethods.length === 0) { + return ; + } + + return ( + t(identifierInputDescriptionMap[identifier])), + })} + footerTermsDisplayPolicies={[AgreeToTermsPolicy.Automatic]} + authOptionsLink={{ + to: `/${experience.routes.register}`, + text: 'description.all_account_creation_options', + }} + > + + + ); +}; + +export default IdentifierRegister; diff --git a/packages/experience/src/pages/IdentifierRegister/use-identifier-sign-up-methods.ts b/packages/experience/src/pages/IdentifierRegister/use-identifier-sign-up-methods.ts new file mode 100644 index 000000000..1a08d0553 --- /dev/null +++ b/packages/experience/src/pages/IdentifierRegister/use-identifier-sign-up-methods.ts @@ -0,0 +1,35 @@ +import { useMemo } from 'react'; + +import useIdentifierParams from '@/hooks/use-identifier-params'; +import { useSieMethods } from '@/hooks/use-sie'; + +/** + * Read sign-up methods from sign-in experience config and URL identifier parameters. + * + * Sign-up methods fallback logic: + * 1. If no identifiers are provided in the URL, return all sign-up methods from sign-in experience config. + * 2. If identifiers are provided in the URL but all of them are not supported by the sign-in experience config, return all sign-up methods from sign-in experience config. + * 3. If identifiers are provided in the URL and supported by the sign-in experience config, return the intersection of the two. + */ +const useIdentifierSignUpMethods = () => { + const { signUpMethods: signUpMethodsFromSie } = useSieMethods(); + const { identifiers } = useIdentifierParams(); + + return useMemo(() => { + // Fallback to all sign up methods if no identifiers are provided + if (identifiers.length === 0) { + return signUpMethodsFromSie; + } + + const methods = signUpMethodsFromSie.filter((identifier) => identifiers.includes(identifier)); + + // Fallback to all sign up methods if no identifiers are supported + if (methods.length === 0) { + return signUpMethodsFromSie; + } + + return methods; + }, [identifiers, signUpMethodsFromSie]); +}; + +export default useIdentifierSignUpMethods; diff --git a/packages/phrases-experience/src/locales/de/description.ts b/packages/phrases-experience/src/locales/de/description.ts index bde66dd9c..3d72bc3aa 100644 --- a/packages/phrases-experience/src/locales/de/description.ts +++ b/packages/phrases-experience/src/locales/de/description.ts @@ -105,6 +105,9 @@ const description = { identifier_sign_in_description: 'Geben Sie Ihre {{types, list(type: disjunction;)}} ein, um sich anzumelden.', all_sign_in_options: 'Alle Anmeldeoptionen', + identifier_register_description: + 'Geben Sie Ihre {{types, list(type: disjunction;)}} ein, um ein neues Konto zu erstellen.', + all_account_creation_options: 'Alle Kontoerstellungsoptionen', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/en/description.ts b/packages/phrases-experience/src/locales/en/description.ts index 8689e7e1e..62ad75401 100644 --- a/packages/phrases-experience/src/locales/en/description.ts +++ b/packages/phrases-experience/src/locales/en/description.ts @@ -90,6 +90,9 @@ const description = { auto_agreement: 'By continuing, you agree to the .', identifier_sign_in_description: 'Enter you {{types, list(type: disjunction;)}} to sign in.', all_sign_in_options: 'All sign-in options', + identifier_register_description: + 'Enter you {{types, list(type: disjunction;)}} to create a new account.', + all_account_creation_options: 'All account creation options', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/es/description.ts b/packages/phrases-experience/src/locales/es/description.ts index fbec1edf2..ad6341bc2 100644 --- a/packages/phrases-experience/src/locales/es/description.ts +++ b/packages/phrases-experience/src/locales/es/description.ts @@ -105,6 +105,9 @@ const description = { identifier_sign_in_description: 'Ingrese su {{types, list(type: disjunction;)}} para iniciar sesión.', all_sign_in_options: 'Todas las opciones de inicio de sesión', + identifier_register_description: + 'Ingrese su {{types, list(type: disjunction;)}} para crear una nueva cuenta.', + all_account_creation_options: 'Todas las opciones de creación de cuenta', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/fr/description.ts b/packages/phrases-experience/src/locales/fr/description.ts index 867e96ec7..748074b7b 100644 --- a/packages/phrases-experience/src/locales/fr/description.ts +++ b/packages/phrases-experience/src/locales/fr/description.ts @@ -105,6 +105,9 @@ const description = { identifier_sign_in_description: 'Entrez votre {{types, list(type: disjunction;)}} pour vous connecter.', all_sign_in_options: 'Toutes les options de connexion', + identifier_register_description: + 'Entrez votre {{types, list(type: disjunction;)}} pour créer un nouveau compte.', + all_account_creation_options: 'Toutes les options de création de compte', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/it/description.ts b/packages/phrases-experience/src/locales/it/description.ts index 8c5fa999e..3a3eed40c 100644 --- a/packages/phrases-experience/src/locales/it/description.ts +++ b/packages/phrases-experience/src/locales/it/description.ts @@ -102,6 +102,9 @@ const description = { identifier_sign_in_description: 'Inserisci il tuo {{types, list(type: disjunction;)}} per accedere.', all_sign_in_options: 'Tutte le opzioni di accesso', + identifier_register_description: + 'Inserisci il tuo {{types, list(type: disjunction;)}} per creare un nuovo account.', + all_account_creation_options: 'Tutte le opzioni di creazione account', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/ja/description.ts b/packages/phrases-experience/src/locales/ja/description.ts index 4bf415507..6a68e572b 100644 --- a/packages/phrases-experience/src/locales/ja/description.ts +++ b/packages/phrases-experience/src/locales/ja/description.ts @@ -101,6 +101,9 @@ const description = { auto_agreement: '続行することで、に同意したことになります。', identifier_sign_in_description: '{{types, list(type: disjunction;)}}を入力してサインインします。', all_sign_in_options: 'すべてのサインインオプション', + identifier_register_description: + '{{types, list(type: disjunction;)}}を入力して新しいアカウントを作成します。', + all_account_creation_options: 'すべてのアカウント作成オプション', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/ko/description.ts b/packages/phrases-experience/src/locales/ko/description.ts index 460663a2c..6a952a4fd 100644 --- a/packages/phrases-experience/src/locales/ko/description.ts +++ b/packages/phrases-experience/src/locales/ko/description.ts @@ -96,6 +96,9 @@ const description = { identifier_sign_in_description: '로그인하려면 {{types, list(type: disjunction;)}}을(를) 입력하세요.', all_sign_in_options: '모든 로그인 옵션', + identifier_register_description: + '새 계정을 만들려면 {{types, list(type: disjunction;)}}을(를) 입력하세요.', + all_account_creation_options: '모든 계정 생성 옵션', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/pl-pl/description.ts b/packages/phrases-experience/src/locales/pl-pl/description.ts index 8ec9d862d..8f1172498 100644 --- a/packages/phrases-experience/src/locales/pl-pl/description.ts +++ b/packages/phrases-experience/src/locales/pl-pl/description.ts @@ -103,6 +103,9 @@ const description = { identifier_sign_in_description: 'Wprowadź swoje {{types, list(type: disjunction;)}} aby się zalogować.', all_sign_in_options: 'Wszystkie opcje logowania', + identifier_register_description: + 'Wprowadź swoje {{types, list(type: disjunction;)}} aby utworzyć nowe konto.', + all_account_creation_options: 'Wszystkie opcje tworzenia konta', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/pt-br/description.ts b/packages/phrases-experience/src/locales/pt-br/description.ts index 2bfa37561..d9e0cb9a7 100644 --- a/packages/phrases-experience/src/locales/pt-br/description.ts +++ b/packages/phrases-experience/src/locales/pt-br/description.ts @@ -99,6 +99,9 @@ const description = { auto_agreement: 'Ao continuar, você concorda com os .', identifier_sign_in_description: 'Digite seu {{types, list(type: disjunction;)}} para entrar.', all_sign_in_options: 'Todas as opções de login', + identifier_register_description: + 'Digite seu {{types, list(type: disjunction;)}} para criar uma nova conta.', + all_account_creation_options: 'Todas as opções de criação de conta', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/pt-pt/description.ts b/packages/phrases-experience/src/locales/pt-pt/description.ts index 70a3d64ad..1991d3825 100644 --- a/packages/phrases-experience/src/locales/pt-pt/description.ts +++ b/packages/phrases-experience/src/locales/pt-pt/description.ts @@ -100,6 +100,9 @@ const description = { identifier_sign_in_description: 'Introduza o seu {{types, list(type: disjunction;)}} para iniciar sessão.', all_sign_in_options: 'Todas as opções de início de sessão', + identifier_register_description: + 'Introduza o seu {{types, list(type: disjunction;)}} para criar uma nova conta.', + all_account_creation_options: 'Todas as opções de criação de conta', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/ru/description.ts b/packages/phrases-experience/src/locales/ru/description.ts index dab9966c3..1f8f6ca8b 100644 --- a/packages/phrases-experience/src/locales/ru/description.ts +++ b/packages/phrases-experience/src/locales/ru/description.ts @@ -103,6 +103,9 @@ const description = { auto_agreement: 'Продолжая, вы соглашаетесь с .', identifier_sign_in_description: 'Введите свои {{types, list(type: disjunction;)}} для входа.', all_sign_in_options: 'Все варианты входа', + identifier_register_description: + 'Введите свои {{types, list(type: disjunction;)}} чтобы создать новую учётную запись.', + all_account_creation_options: 'Все варианты создания учётной записи', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/tr-tr/description.ts b/packages/phrases-experience/src/locales/tr-tr/description.ts index cecb87590..6189a4b26 100644 --- a/packages/phrases-experience/src/locales/tr-tr/description.ts +++ b/packages/phrases-experience/src/locales/tr-tr/description.ts @@ -99,6 +99,9 @@ const description = { auto_agreement: 'Devam ederek kabul etmiş oluyorsunuz.', identifier_sign_in_description: 'Oturum açmak için {{types, list(type: disjunction;)}} girin.', all_sign_in_options: 'Tüm oturum açma seçenekleri', + identifier_register_description: + 'Yeni bir hesap oluşturmak için {{types, list(type: disjunction;)}} girin.', + all_account_creation_options: 'Tüm hesap oluşturma seçenekleri', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/zh-cn/description.ts b/packages/phrases-experience/src/locales/zh-cn/description.ts index f0c9b23ed..a24ae4f85 100644 --- a/packages/phrases-experience/src/locales/zh-cn/description.ts +++ b/packages/phrases-experience/src/locales/zh-cn/description.ts @@ -79,6 +79,8 @@ const description = { auto_agreement: '继续即表示您同意。', identifier_sign_in_description: '输入您的{{types, list(type: disjunction;)}}以登录。', all_sign_in_options: '所有登录选项', + identifier_register_description: '输入您的{{types, list(type: disjunction;)}}以创建新账户。', + all_account_creation_options: '所有账户创建选项', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/zh-hk/description.ts b/packages/phrases-experience/src/locales/zh-hk/description.ts index 1ef2ce8bd..fc9a12330 100644 --- a/packages/phrases-experience/src/locales/zh-hk/description.ts +++ b/packages/phrases-experience/src/locales/zh-hk/description.ts @@ -91,6 +91,8 @@ const description = { auto_agreement: '繼續即表示您同意。', identifier_sign_in_description: '輸入您的{{types, list(type: disjunction;)}}以登入。', all_sign_in_options: '所有登入選項', + identifier_register_description: '輸入您的{{types, list(type: disjunction;)}}以建立新帳戶。', + all_account_creation_options: '所有帳戶創建選項', }; export default Object.freeze(description); diff --git a/packages/phrases-experience/src/locales/zh-tw/description.ts b/packages/phrases-experience/src/locales/zh-tw/description.ts index 23c706afd..89a0e0805 100644 --- a/packages/phrases-experience/src/locales/zh-tw/description.ts +++ b/packages/phrases-experience/src/locales/zh-tw/description.ts @@ -91,6 +91,8 @@ const description = { auto_agreement: '繼續即表示您同意。', identifier_sign_in_description: '輸入您的{{types, list(type: disjunction;)}}以登入。', all_sign_in_options: '所有登入選項', + identifier_register_description: '輸入您的{{types, list(type: disjunction;)}}以建立新帳戶。', + all_account_creation_options: '所有帳戶創建選項', }; export default Object.freeze(description); diff --git a/packages/schemas/src/consts/experience.ts b/packages/schemas/src/consts/experience.ts index c992636b6..2fab4d053 100644 --- a/packages/schemas/src/consts/experience.ts +++ b/packages/schemas/src/consts/experience.ts @@ -4,6 +4,7 @@ const routes = Object.freeze({ sso: 'single-sign-on', consent: 'consent', identifierSignIn: 'identifier-sign-in', + identifierRegister: 'identifier-register', }); export const experience = Object.freeze({