mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(ui): add forget password page (#1943)
* refactor(ui): folder rename temp folder rename temp * fix(ui): folder rename rename UsernameSignin to UsernameSignIn * feat(ui): add forget-password page add forget-password page * test(ui): add page ut add page ut * fix(ui): cr update change forget password to forgot password * chore(ui): hide WIP page path hide WIP page path
This commit is contained in:
parent
de4c46e400
commit
39d80d9912
16 changed files with 183 additions and 21 deletions
|
@ -25,6 +25,7 @@ const translation = {
|
||||||
agree: 'Agree',
|
agree: 'Agree',
|
||||||
got_it: 'Got it',
|
got_it: 'Got it',
|
||||||
sign_in_with: 'Sign in with {{name}}',
|
sign_in_with: 'Sign in with {{name}}',
|
||||||
|
forgot_password: 'Forgot Password?',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'email',
|
email: 'email',
|
||||||
|
@ -35,7 +36,6 @@ const translation = {
|
||||||
agree_with_terms_modal: 'To proceed, please agree to the <link></link>.',
|
agree_with_terms_modal: 'To proceed, please agree to the <link></link>.',
|
||||||
terms_of_use: 'Terms of Use',
|
terms_of_use: 'Terms of Use',
|
||||||
create_account: 'Create Account',
|
create_account: 'Create Account',
|
||||||
forgot_password: 'Forgot Password?',
|
|
||||||
or: 'or',
|
or: 'or',
|
||||||
enter_passcode: 'The passcode has been sent to your {{address}}',
|
enter_passcode: 'The passcode has been sent to your {{address}}',
|
||||||
passcode_sent: 'The passcode has been resent',
|
passcode_sent: 'The passcode has been resent',
|
||||||
|
@ -50,6 +50,11 @@ const translation = {
|
||||||
social_create_account: 'No account? You can create a new account and link.',
|
social_create_account: 'No account? You can create a new account and link.',
|
||||||
social_bind_account: 'Already have an account? Sign in to link it with your social identity.',
|
social_bind_account: 'Already have an account? Sign in to link it with your social identity.',
|
||||||
social_bind_with_existing: 'We find a related account, you can link it directly.',
|
social_bind_with_existing: 'We find a related account, you can link it directly.',
|
||||||
|
reset_password: 'Reset Password',
|
||||||
|
reset_password_description_email:
|
||||||
|
'Enter the email address associated with your account, and we’ll email you the verification code to reset your password.',
|
||||||
|
reset_password_description_sms:
|
||||||
|
'Enter the phone number associated with your account, and we’ll text you the verification code to reset your password.',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
username_password_mismatch: 'Username and password do not match',
|
username_password_mismatch: 'Username and password do not match',
|
||||||
|
|
|
@ -27,6 +27,7 @@ const translation = {
|
||||||
agree: 'Accepter',
|
agree: 'Accepter',
|
||||||
got_it: 'Compris',
|
got_it: 'Compris',
|
||||||
sign_in_with: 'Connexion avec {{name}}',
|
sign_in_with: 'Connexion avec {{name}}',
|
||||||
|
forgot_password: 'Mot de passe oublié ?',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'email',
|
email: 'email',
|
||||||
|
@ -37,7 +38,6 @@ const translation = {
|
||||||
agree_with_terms_modal: 'Pour continuer, veuillez accepter le <link></link>.',
|
agree_with_terms_modal: 'Pour continuer, veuillez accepter le <link></link>.',
|
||||||
terms_of_use: "Conditions d'utilisation",
|
terms_of_use: "Conditions d'utilisation",
|
||||||
create_account: 'Créer un compte',
|
create_account: 'Créer un compte',
|
||||||
forgot_password: 'Mot de passe oublié ?',
|
|
||||||
or: 'ou',
|
or: 'ou',
|
||||||
enter_passcode: 'Le code a été envoyé à {{address}}',
|
enter_passcode: 'Le code a été envoyé à {{address}}',
|
||||||
passcode_sent: 'Le code a été renvoyé',
|
passcode_sent: 'Le code a été renvoyé',
|
||||||
|
@ -54,6 +54,11 @@ const translation = {
|
||||||
'Vous avez déjà un compte ? Connectez-vous pour le relier à votre identité sociale.',
|
'Vous avez déjà un compte ? Connectez-vous pour le relier à votre identité sociale.',
|
||||||
social_bind_with_existing:
|
social_bind_with_existing:
|
||||||
'Nous trouvons un compte connexe, vous pouvez le relier directement.',
|
'Nous trouvons un compte connexe, vous pouvez le relier directement.',
|
||||||
|
reset_password: 'Réinitialiser le mot de passe',
|
||||||
|
reset_password_description_email:
|
||||||
|
"Entrez l'adresse e-mail associée à votre compte et nous vous enverrons par e-mail le code de vérification pour réinitialiser votre mot de passe.",
|
||||||
|
reset_password_description_sms:
|
||||||
|
'Entrez le numéro de téléphone associé à votre compte et nous vous enverrons le code de vérification par SMS pour réinitialiser votre mot de passe.',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
username_password_mismatch: "Le nom d'utilisateur et le mot de passe ne correspondent pas",
|
username_password_mismatch: "Le nom d'utilisateur et le mot de passe ne correspondent pas",
|
||||||
|
|
|
@ -27,6 +27,7 @@ const translation = {
|
||||||
agree: '동의',
|
agree: '동의',
|
||||||
got_it: '알겠습니다',
|
got_it: '알겠습니다',
|
||||||
sign_in_with: '{{name}} 로그인',
|
sign_in_with: '{{name}} 로그인',
|
||||||
|
forgot_password: '비밀번호를 잊어버리셨나요?',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: '이메일',
|
email: '이메일',
|
||||||
|
@ -37,7 +38,6 @@ const translation = {
|
||||||
agree_with_terms_modal: '진행하기 위해서는, 다음을 동의해주세요 <link></link>.',
|
agree_with_terms_modal: '진행하기 위해서는, 다음을 동의해주세요 <link></link>.',
|
||||||
terms_of_use: '이용약관',
|
terms_of_use: '이용약관',
|
||||||
create_account: '계정 생성',
|
create_account: '계정 생성',
|
||||||
forgot_password: '비밀번호를 잊어버리셨나요?',
|
|
||||||
or: '또는',
|
or: '또는',
|
||||||
enter_passcode: '{{address}} 으로 비밀번호가 전송되었어요.',
|
enter_passcode: '{{address}} 으로 비밀번호가 전송되었어요.',
|
||||||
passcode_sent: '비밀번호가 재전송 되었습니다.',
|
passcode_sent: '비밀번호가 재전송 되었습니다.',
|
||||||
|
@ -50,6 +50,11 @@ const translation = {
|
||||||
social_create_account: '계정이 없으신가요? 새로운 계정을 만들고 연동해보세요.',
|
social_create_account: '계정이 없으신가요? 새로운 계정을 만들고 연동해보세요.',
|
||||||
social_bind_account: '계정이 이미 있으신가요? 로그인하여 다른 계정과 연동해보세요.',
|
social_bind_account: '계정이 이미 있으신가요? 로그인하여 다른 계정과 연동해보세요.',
|
||||||
social_bind_with_existing: '관련된 계정을 찾았어요. 해당 계정과 연동할 수 있습니다.',
|
social_bind_with_existing: '관련된 계정을 찾았어요. 해당 계정과 연동할 수 있습니다.',
|
||||||
|
reset_password: '암호를 재설정',
|
||||||
|
reset_password_description_email:
|
||||||
|
'계정과 연결된 이메일 주소를 입력하면 비밀번호 재설정을 위한 인증 코드를 이메일로 보내드립니다.',
|
||||||
|
reset_password_description_sms:
|
||||||
|
'계정과 연결된 전화번호를 입력하면 비밀번호 재설정을 위한 인증 코드를 문자로 보내드립니다.',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
username_password_mismatch: '사용자 이름 또는 비밀번호가 일치하지 않아요.',
|
username_password_mismatch: '사용자 이름 또는 비밀번호가 일치하지 않아요.',
|
||||||
|
|
|
@ -27,6 +27,7 @@ const translation = {
|
||||||
agree: 'Aceito',
|
agree: 'Aceito',
|
||||||
got_it: 'Entendi',
|
got_it: 'Entendi',
|
||||||
sign_in_with: 'Entrar com {{name}}',
|
sign_in_with: 'Entrar com {{name}}',
|
||||||
|
forgot_password: 'Esqueceu a password?',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'email',
|
email: 'email',
|
||||||
|
@ -37,7 +38,6 @@ const translation = {
|
||||||
agree_with_terms_modal: 'Para prosseguir, por favor, concorde com o <link></link>.',
|
agree_with_terms_modal: 'Para prosseguir, por favor, concorde com o <link></link>.',
|
||||||
terms_of_use: 'Termos de uso',
|
terms_of_use: 'Termos de uso',
|
||||||
create_account: 'Criar uma conta',
|
create_account: 'Criar uma conta',
|
||||||
forgot_password: 'Esqueceu a password?',
|
|
||||||
or: 'ou',
|
or: 'ou',
|
||||||
enter_passcode: 'A senha foi enviada para o seu {{address}}',
|
enter_passcode: 'A senha foi enviada para o seu {{address}}',
|
||||||
passcode_sent: 'A senha foi reenviada',
|
passcode_sent: 'A senha foi reenviada',
|
||||||
|
@ -50,6 +50,11 @@ const translation = {
|
||||||
social_create_account: 'Sem conta? Pode criar uma nova e agregar.',
|
social_create_account: 'Sem conta? Pode criar uma nova e agregar.',
|
||||||
social_bind_account: 'Já tem uma conta? Faça login para agregar a sua identidade social.',
|
social_bind_account: 'Já tem uma conta? Faça login para agregar a sua identidade social.',
|
||||||
social_bind_with_existing: 'Encontramos uma conta relacionada, pode agrega-la diretamente.',
|
social_bind_with_existing: 'Encontramos uma conta relacionada, pode agrega-la diretamente.',
|
||||||
|
reset_password: 'Redefinir Password',
|
||||||
|
reset_password_description_email:
|
||||||
|
'Digite o endereço de email associado à sua conta e enviaremos um email com o código de verificação para redefinir sua senha.',
|
||||||
|
reset_password_description_sms:
|
||||||
|
'Digite o número de telefone associado à sua conta e enviaremos uma mensagem de texto com o código de verificação para redefinir sua senha.',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
username_password_mismatch: 'O Utilizador e a password não correspondem',
|
username_password_mismatch: 'O Utilizador e a password não correspondem',
|
||||||
|
|
|
@ -27,6 +27,7 @@ const translation = {
|
||||||
agree: 'Kabul Et',
|
agree: 'Kabul Et',
|
||||||
got_it: 'Anladım',
|
got_it: 'Anladım',
|
||||||
sign_in_with: '{{name}} ile giriş yap',
|
sign_in_with: '{{name}} ile giriş yap',
|
||||||
|
forgot_password: 'Şifremi Unuttum?',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: 'e-posta adresi',
|
email: 'e-posta adresi',
|
||||||
|
@ -37,7 +38,6 @@ const translation = {
|
||||||
agree_with_terms_modal: 'Devam etmek için lütfen <link></link>i kabul edin.',
|
agree_with_terms_modal: 'Devam etmek için lütfen <link></link>i kabul edin.',
|
||||||
terms_of_use: 'Kullanım Koşulları',
|
terms_of_use: 'Kullanım Koşulları',
|
||||||
create_account: 'Hesap Oluştur',
|
create_account: 'Hesap Oluştur',
|
||||||
forgot_password: 'Şifremi Unuttum?',
|
|
||||||
or: 'veya',
|
or: 'veya',
|
||||||
enter_passcode: 'Kod {{address}}inize gönderildi.',
|
enter_passcode: 'Kod {{address}}inize gönderildi.',
|
||||||
passcode_sent: 'Kodunuz yeniden gönderildi.',
|
passcode_sent: 'Kodunuz yeniden gönderildi.',
|
||||||
|
@ -51,6 +51,11 @@ const translation = {
|
||||||
social_create_account: 'Hesabınız yok mu? Yeni bir hesap ve bağlantı oluşturabilirsiniz.',
|
social_create_account: 'Hesabınız yok mu? Yeni bir hesap ve bağlantı oluşturabilirsiniz.',
|
||||||
social_bind_account: 'Hesabınız zaten var mı? Hesabınıza bağlanmak için giriş yapınız.',
|
social_bind_account: 'Hesabınız zaten var mı? Hesabınıza bağlanmak için giriş yapınız.',
|
||||||
social_bind_with_existing: 'İlgili bir hesap bulduk, hemen bağlayabilirsiniz.',
|
social_bind_with_existing: 'İlgili bir hesap bulduk, hemen bağlayabilirsiniz.',
|
||||||
|
reset_password: 'Şifre yenile',
|
||||||
|
reset_password_description_email:
|
||||||
|
'Hesabınızla ilişkili e-posta adresini girin, şifrenizi sıfırlamak için size doğrulama kodunu e-posta ile gönderelim.',
|
||||||
|
reset_password_description_sms:
|
||||||
|
'Hesabınızla ilişkili telefon numarasını girin, şifrenizi sıfırlamak için size doğrulama kodunu kısa mesajla gönderelim.',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
username_password_mismatch: 'Kullanıcı adı ve şifre eşleşmiyor.',
|
username_password_mismatch: 'Kullanıcı adı ve şifre eşleşmiyor.',
|
||||||
|
|
|
@ -27,6 +27,7 @@ const translation = {
|
||||||
agree: '同意',
|
agree: '同意',
|
||||||
got_it: '知道了',
|
got_it: '知道了',
|
||||||
sign_in_with: '通过 {{name}} 登录',
|
sign_in_with: '通过 {{name}} 登录',
|
||||||
|
forgot_password: '忘记密码?',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
email: '邮箱',
|
email: '邮箱',
|
||||||
|
@ -37,7 +38,6 @@ const translation = {
|
||||||
agree_with_terms_modal: '请先同意 <link></link> 以继续',
|
agree_with_terms_modal: '请先同意 <link></link> 以继续',
|
||||||
terms_of_use: '使用条款',
|
terms_of_use: '使用条款',
|
||||||
create_account: '创建帐号',
|
create_account: '创建帐号',
|
||||||
forgot_password: '忘记密码?',
|
|
||||||
or: '或',
|
or: '或',
|
||||||
enter_passcode: '验证码已经发送至你的{{ address }}',
|
enter_passcode: '验证码已经发送至你的{{ address }}',
|
||||||
passcode_sent: '验证码已经发送',
|
passcode_sent: '验证码已经发送',
|
||||||
|
@ -50,6 +50,11 @@ const translation = {
|
||||||
social_create_account: '没有帐号?你可以创建一个帐号并绑定。',
|
social_create_account: '没有帐号?你可以创建一个帐号并绑定。',
|
||||||
social_bind_account: '已有帐号?登录以绑定社交身份。',
|
social_bind_account: '已有帐号?登录以绑定社交身份。',
|
||||||
social_bind_with_existing: '找到了一个匹配的帐号,你可以直接绑定。',
|
social_bind_with_existing: '找到了一个匹配的帐号,你可以直接绑定。',
|
||||||
|
reset_password: '重置密码',
|
||||||
|
reset_password_description_email:
|
||||||
|
'输入与你的帐户关联的电子邮箱地址,我们将通过电子邮件向您发送验证码以重置你的密码。',
|
||||||
|
reset_password_description_sms:
|
||||||
|
'输入与你的帐户关联的电话号码,我们将向您发送验证码以重置你的密码。',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
username_password_mismatch: '用户名和密码不匹配',
|
username_password_mismatch: '用户名和密码不匹配',
|
||||||
|
|
|
@ -57,12 +57,21 @@ const App = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route element={<LoadingLayerProvider />}>
|
<Route element={<LoadingLayerProvider />}>
|
||||||
|
{/* sign-in */}
|
||||||
<Route path="/sign-in" element={<SignIn />} />
|
<Route path="/sign-in" element={<SignIn />} />
|
||||||
<Route path="/sign-in/social/:connector" element={<SocialSignIn />} />
|
<Route path="/sign-in/social/:connector" element={<SocialSignIn />} />
|
||||||
<Route path="/sign-in/:method" element={<SecondarySignIn />} />
|
<Route path="/sign-in/:method" element={<SecondarySignIn />} />
|
||||||
|
|
||||||
|
{/* register */}
|
||||||
<Route path="/register" element={<Register />} />
|
<Route path="/register" element={<Register />} />
|
||||||
<Route path="/register/:method" element={<Register />} />
|
<Route path="/register/:method" element={<Register />} />
|
||||||
|
|
||||||
|
{/* forgot password */}
|
||||||
|
{/**
|
||||||
|
* WIP
|
||||||
|
* <Route path="/forgot-password/:method" element={<ForgotPassword />} />
|
||||||
|
*/}
|
||||||
|
|
||||||
{/* social sign-in pages */}
|
{/* social sign-in pages */}
|
||||||
|
|
||||||
<Route path="/callback/:connector" element={<Callback />} />
|
<Route path="/callback/:connector" element={<Callback />} />
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { termsOfUseConfirmModalPromise } from '@/containers/TermsOfUse/TermsOfUs
|
||||||
import { termsOfUseIframeModalPromise } from '@/containers/TermsOfUse/TermsOfUseIframeModal';
|
import { termsOfUseIframeModalPromise } from '@/containers/TermsOfUse/TermsOfUseIframeModal';
|
||||||
import { TermsOfUseModalMessage } from '@/types';
|
import { TermsOfUseModalMessage } from '@/types';
|
||||||
|
|
||||||
import UsernameSignin from '.';
|
import UsernameSignIn from '.';
|
||||||
|
|
||||||
jest.mock('@/apis/sign-in', () => ({ signInBasic: jest.fn(async () => 0) }));
|
jest.mock('@/apis/sign-in', () => ({ signInBasic: jest.fn(async () => 0) }));
|
||||||
jest.mock('@/containers/TermsOfUse/TermsOfUseConfirmModal', () => ({
|
jest.mock('@/containers/TermsOfUse/TermsOfUseConfirmModal', () => ({
|
||||||
|
@ -21,14 +21,14 @@ jest.mock('@/containers/TermsOfUse/TermsOfUseIframeModal', () => ({
|
||||||
const termsOfUseConfirmModalPromiseMock = termsOfUseConfirmModalPromise as jest.Mock;
|
const termsOfUseConfirmModalPromiseMock = termsOfUseConfirmModalPromise as jest.Mock;
|
||||||
const termsOfUseIframeModalPromiseMock = termsOfUseIframeModalPromise as jest.Mock;
|
const termsOfUseIframeModalPromiseMock = termsOfUseIframeModalPromise as jest.Mock;
|
||||||
|
|
||||||
describe('<UsernameSignin>', () => {
|
describe('<UsernameSignIn>', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('render', () => {
|
test('render', () => {
|
||||||
const { queryByText, container } = renderWithPageContext(<UsernameSignin />);
|
const { queryByText, container } = renderWithPageContext(<UsernameSignIn />);
|
||||||
expect(container.querySelector('input[name="username"]')).not.toBeNull();
|
expect(container.querySelector('input[name="username"]')).not.toBeNull();
|
||||||
expect(container.querySelector('input[name="password"]')).not.toBeNull();
|
expect(container.querySelector('input[name="password"]')).not.toBeNull();
|
||||||
expect(queryByText('action.sign_in')).not.toBeNull();
|
expect(queryByText('action.sign_in')).not.toBeNull();
|
||||||
|
@ -37,14 +37,14 @@ describe('<UsernameSignin>', () => {
|
||||||
test('render with terms settings enabled', () => {
|
test('render with terms settings enabled', () => {
|
||||||
const { queryByText } = renderWithPageContext(
|
const { queryByText } = renderWithPageContext(
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<UsernameSignin />
|
<UsernameSignIn />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
);
|
);
|
||||||
expect(queryByText('description.agree_with_terms')).not.toBeNull();
|
expect(queryByText('description.agree_with_terms')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('required inputs with error message', () => {
|
test('required inputs with error message', () => {
|
||||||
const { queryByText, getByText, container } = renderWithPageContext(<UsernameSignin />);
|
const { queryByText, getByText, container } = renderWithPageContext(<UsernameSignIn />);
|
||||||
const submitButton = getByText('action.sign_in');
|
const submitButton = getByText('action.sign_in');
|
||||||
|
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
@ -72,7 +72,7 @@ describe('<UsernameSignin>', () => {
|
||||||
test('should show terms confirm modal', async () => {
|
test('should show terms confirm modal', async () => {
|
||||||
const { getByText, container } = renderWithPageContext(
|
const { getByText, container } = renderWithPageContext(
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<UsernameSignin />
|
<UsernameSignIn />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
);
|
);
|
||||||
const submitButton = getByText('action.sign_in');
|
const submitButton = getByText('action.sign_in');
|
||||||
|
@ -102,7 +102,7 @@ describe('<UsernameSignin>', () => {
|
||||||
|
|
||||||
const { getByText, container } = renderWithPageContext(
|
const { getByText, container } = renderWithPageContext(
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<UsernameSignin />
|
<UsernameSignIn />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
);
|
);
|
||||||
const submitButton = getByText('action.sign_in');
|
const submitButton = getByText('action.sign_in');
|
||||||
|
@ -132,7 +132,7 @@ describe('<UsernameSignin>', () => {
|
||||||
test('submit form', async () => {
|
test('submit form', async () => {
|
||||||
const { getByText, container } = renderWithPageContext(
|
const { getByText, container } = renderWithPageContext(
|
||||||
<SettingsProvider>
|
<SettingsProvider>
|
||||||
<UsernameSignin />
|
<UsernameSignIn />
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
);
|
);
|
||||||
const submitButton = getByText('action.sign_in');
|
const submitButton = getByText('action.sign_in');
|
|
@ -32,7 +32,7 @@ const defaultState: FieldState = {
|
||||||
password: '',
|
password: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const UsernameSignin = ({ className, autoFocus }: Props) => {
|
const UsernameSignIn = ({ className, autoFocus }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { termsValidation } = useTerms();
|
const { termsValidation } = useTerms();
|
||||||
const {
|
const {
|
||||||
|
@ -121,4 +121,4 @@ const UsernameSignin = ({ className, autoFocus }: Props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UsernameSignin;
|
export default UsernameSignIn;
|
44
packages/ui/src/pages/ForgotPassword/index.module.scss
Normal file
44
packages/ui/src/pages/ForgotPassword/index.module.scss
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
@include _.full-page;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
@include _.full-width;
|
||||||
|
margin-top: _.unit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include _.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-bottom: _.unit(6);
|
||||||
|
color: var(--color-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(body.mobile) {
|
||||||
|
.container {
|
||||||
|
margin-top: _.unit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: _.unit(6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(body.desktop) {
|
||||||
|
.container {
|
||||||
|
margin-top: _.unit(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font: var(--font-title-medium);
|
||||||
|
margin-bottom: _.unit(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font: var(--font-caption);
|
||||||
|
}
|
||||||
|
}
|
32
packages/ui/src/pages/ForgotPassword/index.test.tsx
Normal file
32
packages/ui/src/pages/ForgotPassword/index.test.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { Routes, Route, MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import ForgotPassword from '.';
|
||||||
|
|
||||||
|
describe('ForgotPassword', () => {
|
||||||
|
it('render email forgot password properly', () => {
|
||||||
|
const { queryByText } = render(
|
||||||
|
<MemoryRouter initialEntries={['/forgot-password/email']}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/forgot-password/:method" element={<ForgotPassword />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queryByText('description.reset_password')).not.toBeNull();
|
||||||
|
expect(queryByText('description.reset_password_description_email')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('render sms forgot password properly', () => {
|
||||||
|
const { queryByText } = render(
|
||||||
|
<MemoryRouter initialEntries={['/forgot-password/sms']}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/forgot-password/:method" element={<ForgotPassword />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(queryByText('description.reset_password')).not.toBeNull();
|
||||||
|
expect(queryByText('description.reset_password_description_sms')).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
47
packages/ui/src/pages/ForgotPassword/index.tsx
Normal file
47
packages/ui/src/pages/ForgotPassword/index.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import NavBar from '@/components/NavBar';
|
||||||
|
import ErrorPage from '@/pages/ErrorPage';
|
||||||
|
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
method?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ForgotPassword = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { method = '' } = useParams<Props>();
|
||||||
|
|
||||||
|
const forgotPasswordForm = useMemo(() => {
|
||||||
|
if (method === 'sms') {
|
||||||
|
return <div>Phone Number form</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'email') {
|
||||||
|
return <div>Email Form</div>;
|
||||||
|
}
|
||||||
|
}, [method]);
|
||||||
|
|
||||||
|
if (!['email', 'sms'].includes(method)) {
|
||||||
|
return <ErrorPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<NavBar />
|
||||||
|
<div className={styles.container}>
|
||||||
|
<div className={styles.title}>{t('description.reset_password')}</div>
|
||||||
|
<div className={styles.description}>
|
||||||
|
{t(`description.reset_password_description_${method === 'email' ? 'email' : 'sms'}`)}
|
||||||
|
</div>
|
||||||
|
{forgotPasswordForm}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForgotPassword;
|
|
@ -4,7 +4,7 @@ import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import NavBar from '@/components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
import { PhonePasswordless, EmailPasswordless } from '@/containers/Passwordless';
|
||||||
import UsernameSignin from '@/containers/UsernameSignin';
|
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||||
import ErrorPage from '@/pages/ErrorPage';
|
import ErrorPage from '@/pages/ErrorPage';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -26,7 +26,7 @@ const SecondarySignIn = () => {
|
||||||
return <EmailPasswordless autoFocus type="sign-in" />;
|
return <EmailPasswordless autoFocus type="sign-in" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <UsernameSignin autoFocus />;
|
return <UsernameSignIn autoFocus />;
|
||||||
}, [method]);
|
}, [method]);
|
||||||
|
|
||||||
if (!['email', 'sms', 'username'].includes(method)) {
|
if (!['email', 'sms', 'username'].includes(method)) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { EmailPasswordless, PhonePasswordless } from '@/containers/Passwordless'
|
||||||
import SignInMethodsLink from '@/containers/SignInMethodsLink';
|
import SignInMethodsLink from '@/containers/SignInMethodsLink';
|
||||||
import { PrimarySocialSignIn, SecondarySocialSignIn } from '@/containers/SocialSignIn';
|
import { PrimarySocialSignIn, SecondarySocialSignIn } from '@/containers/SocialSignIn';
|
||||||
import TermsOfUse from '@/containers/TermsOfUse';
|
import TermsOfUse from '@/containers/TermsOfUse';
|
||||||
import UsernameSignin from '@/containers/UsernameSignin';
|
import UsernameSignIn from '@/containers/UsernameSignIn';
|
||||||
import { SignInMethod, LocalSignInMethod } from '@/types';
|
import { SignInMethod, LocalSignInMethod } from '@/types';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
@ -40,7 +40,7 @@ export const PrimarySection = ({
|
||||||
return signInMode === SignInMode.Register ? (
|
return signInMode === SignInMode.Register ? (
|
||||||
<CreateAccount />
|
<CreateAccount />
|
||||||
) : (
|
) : (
|
||||||
<UsernameSignin className={styles.primarySignIn} />
|
<UsernameSignIn className={styles.primarySignIn} />
|
||||||
);
|
);
|
||||||
case 'social':
|
case 'social':
|
||||||
return socialConnectors.length > 0 ? (
|
return socialConnectors.length > 0 ? (
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { SignInExperience, ConnectorMetadata, AppearanceMode } from '@logto/sche
|
||||||
|
|
||||||
export type UserFlow = 'sign-in' | 'register';
|
export type UserFlow = 'sign-in' | 'register';
|
||||||
export type SignInMethod = 'username' | 'email' | 'sms' | 'social';
|
export type SignInMethod = 'username' | 'email' | 'sms' | 'social';
|
||||||
export type LocalSignInMethod = 'username' | 'email' | 'sms';
|
export type LocalSignInMethod = Exclude<SignInMethod, 'social'>;
|
||||||
|
|
||||||
export enum SearchParameters {
|
export enum SearchParameters {
|
||||||
bindWithSocial = 'bind_with',
|
bindWithSocial = 'bind_with',
|
||||||
|
|
Loading…
Add table
Reference in a new issue