Files
frontend-app-authn/src/progressive-profiling/ProgressiveProfiling.jsx
Jesus Balderrama 0d709d1565 feat: React Query migration (#1629)
Move from Redux to React Query across the board.
2026-03-05 13:46:05 -03:00

317 lines
11 KiB
JavaScript

import { useEffect, useState } from 'react';
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
import { identifyAuthenticatedUser, sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
AxiosJwtAuthService,
configure as configureAuth,
getAuthenticatedUser,
} from '@edx/frontend-platform/auth';
import { useIntl } from '@edx/frontend-platform/i18n';
import { getLoggingService } from '@edx/frontend-platform/logging';
import {
Alert,
Form,
Hyperlink,
Spinner,
StatefulButton,
} from '@openedx/paragon';
import { Error } from '@openedx/paragon/icons';
import { Helmet } from 'react-helmet';
import { useLocation } from 'react-router-dom';
import { ProgressiveProfilingProvider, useProgressiveProfilingContext } from './components/ProgressiveProfilingContext';
import messages from './messages';
import ProgressiveProfilingPageModal from './ProgressiveProfilingPageModal';
import BaseContainer from '../base-container';
import { RedirectLogistration } from '../common-components';
import { useSaveUserProfile } from './data/apiHook';
import { ThirdPartyAuthProvider, useThirdPartyAuthContext } from '../common-components/components/ThirdPartyAuthContext';
import { useThirdPartyAuthHook } from '../common-components/data/apiHook';
import {
AUTHN_PROGRESSIVE_PROFILING,
COMPLETE_STATE,
DEFAULT_REDIRECT_URL,
FAILURE_STATE,
PENDING_STATE,
} from '../data/constants';
import isOneTrustFunctionalCookieEnabled from '../data/oneTrust';
import { getAllPossibleQueryParams, isHostAvailableInQueryParams } from '../data/utils';
import { FormFieldRenderer } from '../field-renderer';
const ProgressiveProfilingInner = () => {
const { formatMessage } = useIntl();
const {
thirdPartyAuthApiStatus,
setThirdPartyAuthContextSuccess,
setThirdPartyAuthContextFailure,
optionalFields,
} = useThirdPartyAuthContext();
const welcomePageContext = optionalFields;
const {
submitState,
showError,
success,
} = useProgressiveProfilingContext();
// Hook for saving user profile
const saveUserProfileMutation = useSaveUserProfile();
const location = useLocation();
const registrationEmbedded = isHostAvailableInQueryParams();
const queryParams = getAllPossibleQueryParams();
const authenticatedUser = getAuthenticatedUser() || location.state?.authenticatedUser;
const functionalCookiesConsent = isOneTrustFunctionalCookieEnabled();
const enablePostRegistrationRecommendations = (
getConfig().ENABLE_POST_REGISTRATION_RECOMMENDATIONS && functionalCookiesConsent
);
const [registrationResult, setRegistrationResult] = useState({ redirectUrl: '' });
const [formFieldData, setFormFieldData] = useState({ fields: {}, extendedProfile: [] });
const [values, setValues] = useState({});
const [showModal, setShowModal] = useState(false);
const [showRecommendationsPage, setShowRecommendationsPage] = useState(false);
const { data, isSuccess, error } = useThirdPartyAuthHook(AUTHN_PROGRESSIVE_PROFILING,
{ is_welcome_page: true, next: queryParams?.next });
useEffect(() => {
if (registrationEmbedded) {
if (isSuccess && data) {
setThirdPartyAuthContextSuccess(
data.fieldDescriptions,
data.optionalFields,
data.thirdPartyAuthContext,
);
}
if (error) {
setThirdPartyAuthContextFailure();
}
} else {
configureAuth(AxiosJwtAuthService, { loggingService: getLoggingService(), config: getConfig() });
}
}, [registrationEmbedded, queryParams?.next, isSuccess, data, error,
setThirdPartyAuthContextSuccess, setThirdPartyAuthContextFailure]);
useEffect(() => {
const registrationResponse = location.state?.registrationResult;
if (registrationResponse) {
setRegistrationResult(registrationResponse);
setFormFieldData({
fields: location.state?.optionalFields.fields || {},
extendedProfile: location.state?.optionalFields.extended_profile || [],
});
}
}, [location.state?.registrationResult, location.state?.optionalFields]);
useEffect(() => {
if (registrationEmbedded && welcomePageContext && Object.keys(welcomePageContext).includes('fields')) {
setFormFieldData({
fields: welcomePageContext.fields,
extendedProfile: welcomePageContext.extended_profile,
});
const nextUrl = welcomePageContext.nextUrl ? welcomePageContext.nextUrl : getConfig().SEARCH_CATALOG_URL;
setRegistrationResult({ redirectUrl: nextUrl });
}
}, [registrationEmbedded, welcomePageContext]);
useEffect(() => {
if (authenticatedUser?.userId) {
identifyAuthenticatedUser(authenticatedUser.userId);
sendPageEvent('login_and_registration', 'welcome');
}
}, [authenticatedUser]);
useEffect(() => {
if (!enablePostRegistrationRecommendations) {
sendTrackEvent(
'edx.bi.user.recommendations.not.enabled',
{ functionalCookiesConsent, page: 'authn_recommendations' },
);
return;
}
if (registrationResult.redirectUrl && authenticatedUser?.userId) {
const redirectQueryParams = getAllPossibleQueryParams(registrationResult.redirectUrl);
if (!('enrollment_action' in redirectQueryParams || queryParams?.next)) {
setShowRecommendationsPage(true);
}
}
}, [
authenticatedUser,
enablePostRegistrationRecommendations,
functionalCookiesConsent,
registrationResult.redirectUrl,
queryParams?.next,
]);
if (
!authenticatedUser
|| !(location.state?.registrationResult || registrationEmbedded)
|| thirdPartyAuthApiStatus === FAILURE_STATE
|| (thirdPartyAuthApiStatus === COMPLETE_STATE && !Object.keys(welcomePageContext).includes('fields'))
) {
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
global.location.assign(DASHBOARD_URL);
return null;
}
const handleSubmit = (e) => {
e.preventDefault();
window.history.replaceState(location.state, null, '');
const payload = { ...values, extendedProfile: [] };
if (Object.keys(formFieldData.extendedProfile).length > 0) {
formFieldData.extendedProfile.forEach(fieldName => {
if (values[fieldName]) {
payload.extendedProfile.push({ fieldName, fieldValue: values[fieldName] });
}
delete payload[fieldName];
});
}
saveUserProfileMutation.mutate({ username: authenticatedUser.username, data: snakeCaseObject(payload) });
sendTrackEvent(
'edx.bi.welcome.page.submit.clicked',
{
isGenderSelected: !!values.gender,
isYearOfBirthSelected: !!values.year_of_birth,
isLevelOfEducationSelected: !!values.level_of_education,
isWorkExperienceSelected: !!values.work_experience,
host: queryParams?.host || '',
},
);
};
const handleSkip = (e) => {
e.preventDefault();
window.history.replaceState(location.state, null, '');
setShowModal(true);
sendTrackEvent(
'edx.bi.welcome.page.skip.link.clicked',
{
host: queryParams?.host || '',
},
);
};
const onChangeHandler = (e) => {
if (e.target.type === 'checkbox') {
setValues({ ...values, [e.target.name]: e.target.checked });
} else {
setValues({ ...values, [e.target.name]: e.target.value });
}
};
const formFields = Object.keys(formFieldData.fields).map((fieldName) => {
const fieldData = formFieldData.fields[fieldName];
return (
<span key={fieldData.name}>
<FormFieldRenderer
fieldData={fieldData}
value={values[fieldData.name]}
onChangeHandler={onChangeHandler}
/>
</span>
);
});
const shouldRedirect = success;
return (
<BaseContainer showWelcomeBanner fullName={authenticatedUser?.fullName || authenticatedUser?.name}>
<Helmet>
<title>{formatMessage(messages['progressive.profiling.page.title'],
{ siteName: getConfig().SITE_NAME })}
</title>
</Helmet>
<ProgressiveProfilingPageModal isOpen={showModal} redirectUrl={registrationResult.redirectUrl} />
{(shouldRedirect && welcomePageContext.nextUrl) && (
<RedirectLogistration
success
redirectUrl={registrationResult.redirectUrl}
/>
)}
{shouldRedirect && (
<RedirectLogistration
success
redirectUrl={registrationResult.redirectUrl}
redirectToRecommendationsPage={showRecommendationsPage}
educationLevel={values?.level_of_education}
userId={authenticatedUser?.userId}
/>
)}
<div className="mw-xs m-4 pp-page-content">
{registrationEmbedded && thirdPartyAuthApiStatus === PENDING_STATE ? (
<Spinner animation="border" variant="primary" id="tpa-spinner" />
) : (
<>
<div>
<h2 className="pp-page__heading text-primary">{formatMessage(messages['progressive.profiling.page.heading'])}</h2>
</div><hr className="border-light-700 mb-4" />
{showError ? (
<Alert id="pp-page-errors" className="mb-3" variant="danger" icon={Error}>
<Alert.Heading>{formatMessage(messages['welcome.page.error.heading'])}</Alert.Heading>
<p>{formatMessage(messages['welcome.page.error.message'])}</p>
</Alert>
) : null}
<Form>
{formFields}
{(getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK) && (
<span className="pp-page__support-link">
<Hyperlink
isInline
variant="muted"
destination={getConfig().AUTHN_PROGRESSIVE_PROFILING_SUPPORT_LINK}
target="_blank"
showLaunchIcon={false}
onClick={() => (sendTrackEvent('edx.bi.welcome.page.support.link.clicked'))}
>
{formatMessage(messages['optional.fields.information.link'])}
</Hyperlink>
</span>
)}
<div className="d-flex mt-4 mb-3">
<StatefulButton
type="submit"
variant="brand"
className="pp-page__button-width"
state={submitState}
labels={{
default: showRecommendationsPage ? formatMessage(messages['optional.fields.next.button']) : formatMessage(messages['optional.fields.submit.button']),
pending: '',
}}
onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
<StatefulButton
className="text-gray-700 font-weight-500"
type="submit"
variant="link"
labels={{
default: formatMessage(messages['optional.fields.skip.button']),
}}
onClick={handleSkip}
onMouseDown={(e) => e.preventDefault()}
/>
</div>
</Form>
</>
)}
</div>
</BaseContainer>
);
};
const ProgressiveProfiling = (props) => (
<ThirdPartyAuthProvider>
<ProgressiveProfilingProvider>
<ProgressiveProfilingInner {...props} />
</ProgressiveProfilingProvider>
</ThirdPartyAuthProvider>
);
export default ProgressiveProfiling;