Compare commits

...

5 Commits

Author SHA1 Message Date
Kyr
116830d2c9 fix: username suggestions alignment (#1279)
Co-authored-by: Kyrylo Hudym-Levkovych <kyr.hudym@kyrs-MacBook-Pro.local>
2025-08-08 16:55:59 +05:00
Blue
8efb22595c fix: add new entry for another US label (#1244)
Add new entry for for another US label which is United States
2024-05-03 10:27:32 +05:00
Syed Sajjad Hussain Shah
73e8913f90 feat: remove username from the registration from (#1201) (#1241)
Co-authored-by: Attiya Ishaque <atiya.ishaq@arbisoft.com>
2024-05-02 08:55:56 +05:00
Stanislav Lunyachek
3ddaf795f2 feat: Hide preloaders for third party auth providers if they are disabled 2024-04-19 10:55:48 +05:00
renovate[bot]
a18df02d37 fix(deps): update dependency algoliasearch-helper to v3.17.0 2024-04-11 09:20:07 +00:00
11 changed files with 108 additions and 40 deletions

1
.env
View File

@@ -23,6 +23,7 @@ POST_REGISTRATION_REDIRECT_URL=''
SEARCH_CATALOG_URL='' SEARCH_CATALOG_URL=''
# ***** Features flags ***** # ***** Features flags *****
DISABLE_ENTERPRISE_LOGIN='' DISABLE_ENTERPRISE_LOGIN=''
ENABLE_AUTO_GENERATED_USERNAME=''
ENABLE_DYNAMIC_REGISTRATION_FIELDS='' ENABLE_DYNAMIC_REGISTRATION_FIELDS=''
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN='' ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN=''
ENABLE_POST_REGISTRATION_RECOMMENDATIONS='' ENABLE_POST_REGISTRATION_RECOMMENDATIONS=''

6
package-lock.json generated
View File

@@ -6781,9 +6781,9 @@
} }
}, },
"node_modules/algoliasearch-helper": { "node_modules/algoliasearch-helper": {
"version": "3.16.3", "version": "3.17.0",
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.16.3.tgz", "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.17.0.tgz",
"integrity": "sha512-1OuJT6sONAa9PxcOmWo5WCAT3jQSpCR9/m5Azujja7nhUQwAUDvaaAYrcmUySsrvHh74usZHbE3jFfGnWtZj8w==", "integrity": "sha512-R5422OiQjvjlK3VdpNQ/Qk7KsTIGeM5ACm8civGifOVWdRRV/3SgXuKmeNxe94Dz6fwj/IgpVmXbHutU4mHubg==",
"dependencies": { "dependencies": {
"@algolia/events": "^4.0.1" "@algolia/events": "^4.0.1"
}, },

View File

@@ -37,6 +37,7 @@ const ThirdPartyAuth = (props) => {
const isSocialAuthActive = !!providers.length && !currentProvider; const isSocialAuthActive = !!providers.length && !currentProvider;
const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN; const isEnterpriseLoginDisabled = getConfig().DISABLE_ENTERPRISE_LOGIN;
const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL; const enterpriseLoginURL = getConfig().LMS_BASE_URL + ENTERPRISE_LOGIN_URL;
const isThirdPartyAuthActive = isSocialAuthActive || (isEnterpriseLoginDisabled && isInstitutionAuthActive);
return ( return (
<> <>
@@ -61,7 +62,7 @@ const ThirdPartyAuth = (props) => {
</Hyperlink> </Hyperlink>
)} )}
{thirdPartyAuthApiStatus === PENDING_STATE ? ( {thirdPartyAuthApiStatus === PENDING_STATE && isThirdPartyAuthActive ? (
<div className="mt-4"> <div className="mt-4">
<Skeleton className="tpa-skeleton" height={36} count={2} /> <Skeleton className="tpa-skeleton" height={36} count={2} />
</div> </div>

View File

@@ -4,6 +4,7 @@ const configuration = {
USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '', USER_RETENTION_COOKIE_NAME: process.env.USER_RETENTION_COOKIE_NAME || '',
// Features // Features
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '', DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
ENABLE_AUTO_GENERATED_USERNAME: process.env.ENABLE_AUTO_GENERATED_USERNAME || false,
ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false, ENABLE_DYNAMIC_REGISTRATION_FIELDS: process.env.ENABLE_DYNAMIC_REGISTRATION_FIELDS || false,
ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false, ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN: process.env.ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN || false,
ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false, ENABLE_POST_REGISTRATION_RECOMMENDATIONS: process.env.ENABLE_POST_REGISTRATION_RECOMMENDATIONS || false,

View File

@@ -97,7 +97,7 @@ const CountryField = (props) => {
}; };
const getCountryList = () => countryList.map((country) => ( const getCountryList = () => countryList.map((country) => (
<FormAutosuggestOption key={country[COUNTRY_CODE_KEY]} id={country[COUNTRY_CODE_KEY]}> <FormAutosuggestOption key={country[COUNTRY_DISPLAY_KEY]} id={country[COUNTRY_CODE_KEY]}>
{country[COUNTRY_DISPLAY_KEY]} {country[COUNTRY_DISPLAY_KEY]}
</FormAutosuggestOption> </FormAutosuggestOption>
)); ));

View File

@@ -101,7 +101,7 @@ const UsernameField = (props) => {
}; };
const suggestedUsernames = () => ( const suggestedUsernames = () => (
<div className={className}> <div className={className} role="listbox">
<span className="text-gray username-suggestion--label">{formatMessage(messages['registration.username.suggestion.label'])}</span> <span className="text-gray username-suggestion--label">{formatMessage(messages['registration.username.suggestion.label'])}</span>
<div className="username-scroll-suggested--form-field"> <div className="username-scroll-suggested--form-field">
{usernameSuggestions.map((username, index) => ( {usernameSuggestions.map((username, index) => (
@@ -112,7 +112,9 @@ const UsernameField = (props) => {
className="username-suggestions--chip data-hj-suppress" className="username-suggestions--chip data-hj-suppress"
autoComplete={props.autoComplete} autoComplete={props.autoComplete}
key={`suggestion-${index.toString()}`} key={`suggestion-${index.toString()}`}
tabIndex={0}
onClick={(e) => handleSuggestionClick(e, username)} onClick={(e) => handleSuggestionClick(e, username)}
role="option"
> >
{username} {username}
</Button> </Button>
@@ -123,7 +125,7 @@ const UsernameField = (props) => {
); );
if (usernameSuggestions.length > 0 && errorMessage && value === ' ') { if (usernameSuggestions.length > 0 && errorMessage && value === ' ') {
className = 'username-suggestions__error'; className = 'username-suggestions';
iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />; iconButton = <IconButton src={Close} iconAs={Icon} alt="Close" onClick={() => handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />;
suggestedUsernameDiv = suggestedUsernames(); suggestedUsernameDiv = suggestedUsernames();
} else if (usernameSuggestions.length > 0 && value === ' ') { } else if (usernameSuggestions.length > 0 && value === ' ') {
@@ -134,14 +136,15 @@ const UsernameField = (props) => {
suggestedUsernameDiv = suggestedUsernames(); suggestedUsernameDiv = suggestedUsernames();
} }
return ( return (
<FormGroup <div className="username__form-group-wrapper">
{...props}
handleChange={handleOnChange}
handleFocus={handleOnFocus}
handleBlur={handleOnBlur}
>
{suggestedUsernameDiv} {suggestedUsernameDiv}
</FormGroup> <FormGroup
{...props}
handleChange={handleOnChange}
handleFocus={handleOnFocus}
handleBlur={handleOnBlur}
/>
</div>
); );
}; };

View File

@@ -60,6 +60,7 @@ const RegistrationPage = (props) => {
showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS, showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS,
showConfigurableRegistrationFields: getConfig().ENABLE_DYNAMIC_REGISTRATION_FIELDS, showConfigurableRegistrationFields: getConfig().ENABLE_DYNAMIC_REGISTRATION_FIELDS,
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN, showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
autoGeneratedUsernameEnabled: getConfig().ENABLE_AUTO_GENERATED_USERNAME,
}; };
const { const {
handleInstitutionLogin, handleInstitutionLogin,
@@ -215,6 +216,9 @@ const RegistrationPage = (props) => {
delete payload.password; delete payload.password;
payload.social_auth_provider = currentProvider; payload.social_auth_provider = currentProvider;
} }
if (flags.autoGeneratedUsernameEnabled) {
delete payload.username;
}
// Validating form data before submitting // Validating form data before submitting
const { isValid, fieldErrors, emailSuggestion } = isFormValid( const { isValid, fieldErrors, emailSuggestion } = isFormValid(
@@ -324,16 +328,18 @@ const RegistrationPage = (props) => {
helpText={[formatMessage(messages['help.text.email'])]} helpText={[formatMessage(messages['help.text.email'])]}
floatingLabel={formatMessage(messages['registration.email.label'])} floatingLabel={formatMessage(messages['registration.email.label'])}
/> />
<UsernameField {!flags.autoGeneratedUsernameEnabled && (
name="username" <UsernameField
spellCheck="false" name="username"
value={formFields.username} spellCheck="false"
handleChange={handleOnChange} value={formFields.username}
handleErrorChange={handleErrorChange} handleChange={handleOnChange}
errorMessage={errors.username} handleErrorChange={handleErrorChange}
helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]} errorMessage={errors.username}
floatingLabel={formatMessage(messages['registration.username.label'])} helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]}
/> floatingLabel={formatMessage(messages['registration.username.label'])}
/>
)}
{!currentProvider && ( {!currentProvider && (
<PasswordField <PasswordField
name="password" name="password"

View File

@@ -134,9 +134,16 @@ describe('RegistrationPage', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => { const populateRequiredFields = (
getByLabelText,
payload,
isThirdPartyAuth = false,
autoGeneratedUsernameEnabled = false,
) => {
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } }); fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } });
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } }); if (!autoGeneratedUsernameEnabled) {
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
}
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } }); fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
fireEvent.change(getByLabelText('Country/Region'), { target: { value: payload.country, name: 'country' } }); fireEvent.change(getByLabelText('Country/Region'), { target: { value: payload.country, name: 'country' } });
@@ -299,6 +306,44 @@ describe('RegistrationPage', () => {
}); });
}); });
it('should submit form without UsernameField when autoGeneratedUsernameEnabled is true', () => {
mergeConfig({
ENABLE_AUTO_GENERATED_USERNAME: true,
});
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const payload = {
name: 'John Doe',
email: 'john.doe@gmail.com',
password: 'password1',
country: 'Pakistan',
honor_code: true,
totalRegistrationTime: 0,
};
store.dispatch = jest.fn(store.dispatch);
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(getByLabelText, payload, false, true);
const button = container.querySelector('button.btn-brand');
fireEvent.click(button);
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...payload, country: 'PK' }));
mergeConfig({
ENABLE_AUTO_GENERATED_USERNAME: false,
});
});
it('should not display UsernameField when ENABLE_AUTO_GENERATED_USERNAME is true', () => {
mergeConfig({
ENABLE_AUTO_GENERATED_USERNAME: true,
});
const { queryByLabelText } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(queryByLabelText('Username')).toBeNull();
mergeConfig({
ENABLE_AUTO_GENERATED_USERNAME: false,
});
});
it('should not dispatch registerNewUser on empty form Submission', () => { it('should not dispatch registerNewUser on empty form Submission', () => {
store.dispatch = jest.fn(store.dispatch); store.dispatch = jest.fn(store.dispatch);

View File

@@ -33,7 +33,11 @@ const ConfigurableRegistrationForm = (props) => {
autoSubmitRegistrationForm, autoSubmitRegistrationForm,
} = props; } = props;
const countryList = useMemo(() => getCountryList(getLocale()), []); /** The reason for adding the entry 'United States' is that Chrome browser aut-fill the form with the 'Unites
States' instead of 'United States of America' which does not exist in country dropdown list and gets the user
confused and unable to create an account. So we added the United States entry in the dropdown list.
*/
const countryList = useMemo(() => getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]), []);
let showTermsOfServiceAndHonorCode = false; let showTermsOfServiceAndHonorCode = false;
let showCountryField = false; let showCountryField = false;

View File

@@ -1,11 +1,14 @@
import { camelCaseObject } from '@edx/frontend-platform'; import { camelCaseObject } from '@edx/frontend-platform';
import { logError, logInfo } from '@edx/frontend-platform/logging'; import { logError, logInfo } from '@edx/frontend-platform/logging';
import { call, put, takeEvery } from 'redux-saga/effects'; import {
call, put, race, take, takeEvery,
} from 'redux-saga/effects';
import { import {
fetchRealtimeValidationsBegin, fetchRealtimeValidationsBegin,
fetchRealtimeValidationsFailure, fetchRealtimeValidationsFailure,
fetchRealtimeValidationsSuccess, fetchRealtimeValidationsSuccess,
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_FORM_VALIDATIONS, REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER, REGISTER_NEW_USER,
registerNewUserBegin, registerNewUserBegin,
@@ -41,9 +44,15 @@ export function* handleNewUserRegistration(action) {
export function* fetchRealtimeValidations(action) { export function* fetchRealtimeValidations(action) {
try { try {
yield put(fetchRealtimeValidationsBegin()); yield put(fetchRealtimeValidationsBegin());
const { fieldValidations } = yield call(getFieldsValidations, action.payload.formPayload);
yield put(fetchRealtimeValidationsSuccess(camelCaseObject(fieldValidations))); const { response } = yield race({
response: call(getFieldsValidations, action.payload.formPayload),
cancel: take(REGISTER_CLEAR_USERNAME_SUGGESTIONS),
});
if (response) {
yield put(fetchRealtimeValidationsSuccess(camelCaseObject(response.fieldValidations)));
}
} catch (e) { } catch (e) {
if (e.response && e.response.status === 403) { if (e.response && e.response.status === 403) {
yield put(fetchRealtimeValidationsFailure()); yield put(fetchRealtimeValidationsFailure());

View File

@@ -65,10 +65,15 @@
margin-right: 0.25rem; margin-right: 0.25rem;
} }
.username-suggestions { .username__form-group-wrapper {
position: relative; position: relative;
margin-top: -2.5rem; }
margin-left: 15px;
.username-suggestions {
position: absolute;
inset: 0;
padding-left: 15px;
z-index: 100;
} }
.username-suggestions__close__button { .username-suggestions__close__button {
@@ -76,13 +81,6 @@
position: absolute; position: absolute;
} }
.username-suggestions__error {
position: relative;
margin-top: -13.7%;
margin-bottom: 11%;
margin-left: 15px;
}
.username-scroll-suggested--form-field { .username-scroll-suggested--form-field {
width: 20rem; width: 20rem;
white-space: nowrap; white-space: nowrap;