diff --git a/packages/core/src/utils/translation.test.ts b/packages/core/src/utils/translation.test.ts index 9fe10346e..59b5018ad 100644 --- a/packages/core/src/utils/translation.test.ts +++ b/packages/core/src/utils/translation.test.ts @@ -3,24 +3,16 @@ import fr from '@logto/phrases-ui/lib/locales/fr.js'; import { isStrictlyPartial } from '#src/utils/translation.js'; -const customizedFrTranslation = { - secondary: { - sign_in_with: 'Customized value A', - social_bind_with: 'Customized value B', - }, -}; - describe('isStrictlyPartial', () => { it('should be true when its structure is valid', () => { expect(isStrictlyPartial(en.translation, fr.translation)).toBeTruthy(); - expect(isStrictlyPartial(en.translation, customizedFrTranslation)).toBeTruthy(); }); it('should be true when the structure is partial and the existing key-value pairs are correct', () => { expect( isStrictlyPartial(en.translation, { secondary: { - sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}', + social_bind_with: 'Se connecter avec {{methods, list(type: disjunction;)}}', // Missing 'secondary.social_bind_with' key-value pair }, }) @@ -31,9 +23,6 @@ describe('isStrictlyPartial', () => { expect( isStrictlyPartial(en.translation, { secondary: { - sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}', - social_bind_with: - 'Vous avez déjà un compte ? Connectez-vous pour lier {{methods, list(type: disjunction;)}} avec votre identité sociale.', foo: 'bar', // Unexpected key-value pair }, }) diff --git a/packages/integration-tests/src/tests/ui/smoke.test.ts b/packages/integration-tests/src/tests/ui/smoke.test.ts index a59bc9fed..ce732754d 100644 --- a/packages/integration-tests/src/tests/ui/smoke.test.ts +++ b/packages/integration-tests/src/tests/ui/smoke.test.ts @@ -24,8 +24,8 @@ describe('smoke testing', () => { expect(page.url()).toBe(new URL('register', logtoConsoleUrl).href); - const usernameField = await page.waitForSelector('input[name=new-username]'); - const submitButton = await page.waitForSelector('button'); + const usernameField = await page.waitForSelector('input[name=identifier]'); + const submitButton = await page.waitForSelector('button[name=submit]'); await usernameField.type(consoleUsername); @@ -33,7 +33,7 @@ describe('smoke testing', () => { await submitButton.click(); await navigateToSignIn; - expect(page.url()).toBe(new URL('register/username/password', logtoConsoleUrl).href); + expect(page.url()).toBe(new URL('register/password', logtoConsoleUrl).href); const passwordField = await page.waitForSelector('input[name=newPassword]'); const confirmPasswordField = await page.waitForSelector('input[name=confirmPassword]'); diff --git a/packages/phrases-ui/src/locales/de.ts b/packages/phrases-ui/src/locales/de.ts index d0b077503..b6c78afd5 100644 --- a/packages/phrases-ui/src/locales/de.ts +++ b/packages/phrases-ui/src/locales/de.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: 'Passwort bestätigen', }, secondary: { - sign_in_with: 'Anmelden mit {{methods, list(type: disjunction;)}}', - register_with: 'Konto mit {{methods, list(type: disjunction;)}} erstellen', social_bind_with: 'Besitzt du schon ein Konto? Melde dich an, um {{methods, list(type: disjunction;)}} mit deiner Identität zu verbinden.', }, diff --git a/packages/phrases-ui/src/locales/en.ts b/packages/phrases-ui/src/locales/en.ts index 3f6a25028..f1df8e581 100644 --- a/packages/phrases-ui/src/locales/en.ts +++ b/packages/phrases-ui/src/locales/en.ts @@ -7,8 +7,6 @@ const translation = { confirm_password: 'Confirm password', }, secondary: { - sign_in_with: 'Sign in with {{methods, list(type: disjunction;)}}', - register_with: 'Create account with {{methods, list(type: disjunction;)}}', social_bind_with: 'Already had an account? Sign in to link {{methods, list(type: disjunction;)}} with your social identity.', }, diff --git a/packages/phrases-ui/src/locales/fr.ts b/packages/phrases-ui/src/locales/fr.ts index 6ab81d6e4..3bae0f723 100644 --- a/packages/phrases-ui/src/locales/fr.ts +++ b/packages/phrases-ui/src/locales/fr.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: 'Confirmer le mot de passe', }, secondary: { - sign_in_with: 'Se connecter avec {{methods, list(type: disjunction;)}}', - register_with: 'Create account with {{methods, list(type: disjunction;)}}', // UNTRANSLATED social_bind_with: 'Vous avez déjà un compte ? Connectez-vous pour lier {{methods, list(type: disjunction;)}} avec votre identité sociale.', }, diff --git a/packages/phrases-ui/src/locales/ko.ts b/packages/phrases-ui/src/locales/ko.ts index 05b29a89c..961c7e875 100644 --- a/packages/phrases-ui/src/locales/ko.ts +++ b/packages/phrases-ui/src/locales/ko.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: '비밀번호 확인', }, secondary: { - sign_in_with: '{{methods, list(type: disjunction;)}} 로그인', - register_with: '{{methods, list(type: disjunction;)}} 회원가입', social_bind_with: '이미 계정이 있으신가요? {{methods, list(type: disjunction;)}}로 로그인 해보세요!', }, diff --git a/packages/phrases-ui/src/locales/pt-br.ts b/packages/phrases-ui/src/locales/pt-br.ts index f892758d4..b72ff68d7 100644 --- a/packages/phrases-ui/src/locales/pt-br.ts +++ b/packages/phrases-ui/src/locales/pt-br.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: 'Confirme a senha', }, secondary: { - sign_in_with: 'Entrar com {{methods, list(type: disjunction;)}}', - register_with: 'Criar conta com {{methods, list(type: disjunction;)}}', social_bind_with: 'Já tinha uma conta? Faça login no link {{methods, list(type: disjunction;)}} com sua identidade social.', }, diff --git a/packages/phrases-ui/src/locales/pt-pt.ts b/packages/phrases-ui/src/locales/pt-pt.ts index f99e8fe91..65af47dee 100644 --- a/packages/phrases-ui/src/locales/pt-pt.ts +++ b/packages/phrases-ui/src/locales/pt-pt.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: 'Confirmar password', }, secondary: { - sign_in_with: 'Entrar com {{methods, list(type: disjunction;)}}', - register_with: 'Create account with {{methods, list(type: disjunction;)}}', // UNTRANSLATED social_bind_with: 'Já tem uma conta? Faça login para agregar {{methods, list(type: disjunction;)}} com a sua identidade social.', }, diff --git a/packages/phrases-ui/src/locales/tr-tr.ts b/packages/phrases-ui/src/locales/tr-tr.ts index 6c313b584..f69ea2f23 100644 --- a/packages/phrases-ui/src/locales/tr-tr.ts +++ b/packages/phrases-ui/src/locales/tr-tr.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: 'Şifreyi Doğrula', }, secondary: { - sign_in_with: '{{methods, list(type: disjunction;)}} ile giriş yapınız', - register_with: 'Create account with {{methods, list(type: disjunction;)}}', // UNTRANSLATED social_bind_with: 'Hesabınız zaten var mı? {{methods, list(type: disjunction;)}} bağlantısına tıklayarak giriş yapabilirsiniz', }, diff --git a/packages/phrases-ui/src/locales/zh-cn.ts b/packages/phrases-ui/src/locales/zh-cn.ts index 590537039..1c1d7dd4f 100644 --- a/packages/phrases-ui/src/locales/zh-cn.ts +++ b/packages/phrases-ui/src/locales/zh-cn.ts @@ -9,8 +9,6 @@ const translation = { confirm_password: '确认密码', }, secondary: { - sign_in_with: '通过 {{methods, list(type: disjunction;), zhOrSpaces}} 登录', - register_with: '通过 {{methods, list(type: disjunction;)}} 注册', social_bind_with: '绑定到已有账户? 使用 {{methods, list(type: disjunction;), zhOrSpaces}} 登录并绑定。', }, diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index bc643d3e7..5fd158467 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -14,10 +14,9 @@ import Continue from './pages/Continue'; import ContinueWithEmailOrPhone from './pages/Continue/EmailOrPhone'; import ErrorPage from './pages/ErrorPage'; import ForgotPassword from './pages/ForgotPassword'; -import PasswordRegisterWithUsername from './pages/PasswordRegisterWithUsername'; import Register from './pages/Register'; +import RegisterPassword from './pages/RegisterPassword'; import ResetPassword from './pages/ResetPassword'; -import SecondaryRegister from './pages/SecondaryRegister'; import SignIn from './pages/SignIn'; import SignInPassword from './pages/SignInPassword'; import SocialLanding from './pages/SocialLanding'; @@ -88,8 +87,7 @@ const App = () => { index element={isSignInOnly ? : } /> - } /> - } /> + } /> {/* Forgot password */} diff --git a/packages/ui/src/containers/CreateAccount/index.module.scss b/packages/ui/src/containers/CreateAccount/index.module.scss deleted file mode 100644 index 1c791b482..000000000 --- a/packages/ui/src/containers/CreateAccount/index.module.scss +++ /dev/null @@ -1,27 +0,0 @@ -@use '@/scss/underscore' as _; - -.form { - @include _.flex-column; - - > * { - width: 100%; - } - - .inputField { - margin-bottom: _.unit(4); - } - - .formFields { - margin-bottom: _.unit(8); - } - - .terms { - margin-bottom: _.unit(4); - } -} - -:global(body.desktop) { - .formFields { - margin-bottom: _.unit(2); - } -} diff --git a/packages/ui/src/containers/CreateAccount/index.test.tsx b/packages/ui/src/containers/CreateAccount/index.test.tsx deleted file mode 100644 index 33ecbf3d5..000000000 --- a/packages/ui/src/containers/CreateAccount/index.test.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { fireEvent, waitFor, act } from '@testing-library/react'; - -import renderWithPageContext from '@/__mocks__/RenderWithPageContext'; -import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider'; -import { registerWithUsernamePassword } from '@/apis/interaction'; - -import CreateAccount from '.'; - -jest.mock('@/apis/interaction', () => ({ - registerWithUsernamePassword: jest.fn(async () => ({ redirectTo: '/' })), -})); - -describe('', () => { - test('default render', () => { - const { queryByText, container } = renderWithPageContext(); - expect(container.querySelector('input[name="new-username"]')).not.toBeNull(); - expect(container.querySelector('input[name="new-password"]')).not.toBeNull(); - expect(container.querySelector('input[name="confirm-new-password"]')).not.toBeNull(); - expect(queryByText('action.create_account')).not.toBeNull(); - }); - - test('render with terms settings enabled', () => { - const { queryByText } = renderWithPageContext( - - - - ); - expect(queryByText('description.terms_of_use')).not.toBeNull(); - }); - - test('username and password are required', () => { - const { queryByText, getByText } = renderWithPageContext(); - const submitButton = getByText('action.create_account'); - fireEvent.click(submitButton); - - expect(queryByText('username_required')).not.toBeNull(); - expect(queryByText('password_required')).not.toBeNull(); - - expect(registerWithUsernamePassword).not.toBeCalled(); - }); - - test('username with initial numeric char should throw', () => { - const { queryByText, getByText, container } = renderWithPageContext(); - const submitButton = getByText('action.create_account'); - - const usernameInput = container.querySelector('input[name="new-username"]'); - - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: '1username' } }); - } - - fireEvent.click(submitButton); - - expect(queryByText('username_should_not_start_with_number')).not.toBeNull(); - - expect(registerWithUsernamePassword).not.toBeCalled(); - - // Clear error - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: 'username' } }); - } - - expect(queryByText('username_should_not_start_with_number')).toBeNull(); - }); - - test('username with special character should throw', () => { - const { queryByText, getByText, container } = renderWithPageContext(); - const submitButton = getByText('action.create_account'); - const usernameInput = container.querySelector('input[name="new-username"]'); - - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: '@username' } }); - } - - fireEvent.click(submitButton); - - expect(queryByText('username_invalid_charset')).not.toBeNull(); - - expect(registerWithUsernamePassword).not.toBeCalled(); - - // Clear error - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: 'username' } }); - } - - expect(queryByText('username_invalid_charset')).toBeNull(); - }); - - test('password less than 6 chars should throw', () => { - const { queryByText, getByText, container } = renderWithPageContext(); - const submitButton = getByText('action.create_account'); - const passwordInput = container.querySelector('input[name="new-password"]'); - - if (passwordInput) { - fireEvent.change(passwordInput, { target: { value: '12345' } }); - } - - fireEvent.click(submitButton); - - expect(queryByText('password_min_length')).not.toBeNull(); - - expect(registerWithUsernamePassword).not.toBeCalled(); - - // Clear error - if (passwordInput) { - fireEvent.change(passwordInput, { target: { value: '123456' } }); - } - - expect(queryByText('password_min_length')).toBeNull(); - }); - - test('password mismatch with confirmPassword should throw', () => { - const { queryByText, getByText, container } = renderWithPageContext(); - const submitButton = getByText('action.create_account'); - const passwordInput = container.querySelector('input[name="new-password"]'); - const confirmPasswordInput = container.querySelector('input[name="confirm-new-password"]'); - const usernameInput = container.querySelector('input[name="username"]'); - - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: 'username' } }); - } - - if (passwordInput) { - fireEvent.change(passwordInput, { target: { value: '123456' } }); - } - - if (confirmPasswordInput) { - fireEvent.change(confirmPasswordInput, { target: { value: '012345' } }); - } - - fireEvent.click(submitButton); - - expect(queryByText('passwords_do_not_match')).not.toBeNull(); - - expect(registerWithUsernamePassword).not.toBeCalled(); - - // Clear Error - if (confirmPasswordInput) { - fireEvent.change(confirmPasswordInput, { target: { value: '123456' } }); - } - - expect(queryByText('passwords_do_not_match')).toBeNull(); - }); - - test('should clear value when click clear button', async () => { - const { queryByText, getByText, container } = renderWithPageContext(); - - const passwordInput = container.querySelector('input[name="new-password"]'); - const confirmPasswordInput = container.querySelector('input[name="confirm-new-password"]'); - const usernameInput = container.querySelector('input[name="new-username"]'); - const submitButton = getByText('action.create_account'); - - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: 'username' } }); - } - - if (passwordInput) { - fireEvent.change(passwordInput, { target: { value: '123456' } }); - } - - if (confirmPasswordInput) { - fireEvent.change(confirmPasswordInput, { target: { value: '123456' } }); - } - - const confirmClearButton = confirmPasswordInput?.parentElement?.querySelector('svg'); - const usernameClearButton = usernameInput?.parentElement?.querySelector('svg'); - const passwordClearButton = passwordInput?.parentElement?.querySelector('svg'); - - if (confirmClearButton) { - fireEvent.mouseDown(confirmClearButton); - } - - await waitFor(() => { - fireEvent.click(submitButton); - }); - - expect(queryByText('passwords_do_not_match')).not.toBeNull(); - - if (usernameClearButton) { - fireEvent.mouseDown(usernameClearButton); - } - - if (passwordClearButton) { - fireEvent.mouseDown(passwordClearButton); - } - - await waitFor(() => { - fireEvent.click(submitButton); - }); - - expect(queryByText('username_required')).not.toBeNull(); - expect(queryByText('password_required')).not.toBeNull(); - }); - - test('submit form properly with terms settings enabled', async () => { - const { getByText, container } = renderWithPageContext( - - - - ); - const submitButton = getByText('action.create_account'); - const passwordInput = container.querySelector('input[name="new-password"]'); - const confirmPasswordInput = container.querySelector('input[name="confirm-new-password"]'); - const usernameInput = container.querySelector('input[name="new-username"]'); - - if (usernameInput) { - fireEvent.change(usernameInput, { target: { value: 'username' } }); - } - - if (passwordInput) { - fireEvent.change(passwordInput, { target: { value: '123456' } }); - } - - if (confirmPasswordInput) { - fireEvent.change(confirmPasswordInput, { target: { value: '123456' } }); - } - - const termsButton = getByText('description.agree_with_terms'); - fireEvent.click(termsButton); - - act(() => { - fireEvent.click(submitButton); - }); - - await waitFor(() => { - expect(registerWithUsernamePassword).toBeCalledWith('username', '123456'); - }); - }); -}); diff --git a/packages/ui/src/containers/CreateAccount/index.tsx b/packages/ui/src/containers/CreateAccount/index.tsx deleted file mode 100644 index 6dc9aa9e1..000000000 --- a/packages/ui/src/containers/CreateAccount/index.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import classNames from 'classnames'; -import { useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { registerWithUsernamePassword } from '@/apis/interaction'; -import Button from '@/components/Button'; -import Input from '@/components/Input'; -import TermsOfUse from '@/containers/TermsOfUse'; -import useApi from '@/hooks/use-api'; -import type { ErrorHandlers } from '@/hooks/use-error-handler'; -import useErrorHandler from '@/hooks/use-error-handler'; -import useForm from '@/hooks/use-form'; -import useTerms from '@/hooks/use-terms'; -import { - validateUsername, - passwordValidation, - confirmPasswordValidation, -} from '@/utils/field-validations'; - -import * as styles from './index.module.scss'; - -type Props = { - className?: string; - // eslint-disable-next-line react/boolean-prop-naming - autoFocus?: boolean; -}; - -type FieldState = { - username: string; - password: string; - confirmPassword: string; -}; - -const defaultState: FieldState = { - username: '', - password: '', - confirmPassword: '', -}; - -const CreateAccount = ({ className, autoFocus }: Props) => { - const { t } = useTranslation(); - const { termsValidation } = useTerms(); - const { - fieldValue, - setFieldValue, - setFieldErrors, - register: fieldRegister, - validateForm, - } = useForm(defaultState); - - const asyncRegister = useApi(registerWithUsernamePassword); - const handleError = useErrorHandler(); - - const registerErrorHandlers: ErrorHandlers = useMemo( - () => ({ - 'user.username_already_in_use': () => { - setFieldErrors((state) => ({ - ...state, - username: 'username_exists', - })); - }, - }), - [setFieldErrors] - ); - - const onSubmitHandler = useCallback( - async (event?: React.FormEvent) => { - event?.preventDefault(); - - if (!validateForm()) { - return; - } - - if (!(await termsValidation())) { - return; - } - - const [error, result] = await asyncRegister(fieldValue.username, fieldValue.password); - - if (error) { - await handleError(error, registerErrorHandlers); - - return; - } - - if (result?.redirectTo) { - window.location.replace(result.redirectTo); - } - }, - [validateForm, termsValidation, asyncRegister, fieldValue, handleError, registerErrorHandlers] - ); - - return ( -
- { - setFieldValue((state) => ({ ...state, username: '' })); - }} - /> - { - setFieldValue((state) => ({ ...state, password: '' })); - }} - /> - - confirmPasswordValidation(fieldValue.password, confirmPassword) - )} - isErrorStyling={false} - onClear={() => { - setFieldValue((state) => ({ ...state, confirmPassword: '' })); - }} - /> - - - -