Compare commits

...

3 Commits

Author SHA1 Message Date
Stanislav
90ed944f34 feat: Fixed displaying banner and certificates on mobile view (#999)
* feat: fixed displaing banner and certificates on mobile view
* feat: update snapshot

---------

Co-authored-by: vladislavkeblysh <vladislav.keblysh@raccoongang.com>
2024-04-15 09:02:03 -04:00
Stanislav
8ea150d480 fix: Add ID attribute to the main content (#991)
Co-authored-by: Stanislav Lunyachek <lunyachek@MacBook-Pro-M1.local>
2024-03-26 14:27:57 -04:00
vladislavkeblysh
ac277ee798 feat: Fields visibility and visibility forms (#921)
* feat: fixed displaying field and visibility forms
* feat: update snapshot
* feat: fixed tests
2024-01-04 13:41:31 -05:00
8 changed files with 406 additions and 86 deletions

View File

@@ -32,7 +32,7 @@ subscribe(APP_READY, () => {
<AppProvider store={configureStore()}>
<Head />
<Header />
<main>
<main id="main">
<AppRoutes />
</main>
<Footer />

View File

@@ -1,5 +1,5 @@
import React from 'react';
const Banner = () => <div className="profile-page-bg-banner bg-primary d-none d-md-block p-relative" />;
const Banner = () => <div className="profile-page-bg-banner bg-primary d-md-block p-relative" />;
export default Banner;

View File

@@ -178,6 +178,7 @@ class ProfilePage extends React.Component {
visibilityLearningGoal,
languageProficiencies,
visibilityLanguageProficiencies,
courseCertificates,
visibilityCourseCertificates,
bio,
visibilityBio,
@@ -196,6 +197,17 @@ class ProfilePage extends React.Component {
changeHandler: this.handleChange,
};
const isBlockVisible = (blockInfo) => this.isAuthenticatedUserProfile()
|| (!this.isAuthenticatedUserProfile() && Boolean(blockInfo));
const isLanguageBlockVisible = isBlockVisible(languageProficiencies.length);
const isEducationBlockVisible = isBlockVisible(levelOfEducation);
const isSocialLinksBLockVisible = isBlockVisible(socialLinks.some((link) => link.socialLink !== null));
const isBioBlockVisible = isBlockVisible(bio);
const isCertificatesBlockVisible = isBlockVisible(courseCertificates.length);
const isNameBlockVisible = isBlockVisible(name);
const isLocationBlockVisible = isBlockVisible(country);
return (
<div className="container-fluid">
<div className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0">
@@ -230,46 +242,58 @@ class ProfilePage extends React.Component {
<div className="d-md-none mb-4">
{this.renderViewMyRecordsButton()}
</div>
<Name
name={name}
visibilityName={visibilityName}
formId="name"
{...commonFormProps}
/>
<Country
country={country}
visibilityCountry={visibilityCountry}
formId="country"
{...commonFormProps}
/>
<PreferredLanguage
languageProficiencies={languageProficiencies}
visibilityLanguageProficiencies={visibilityLanguageProficiencies}
formId="languageProficiencies"
{...commonFormProps}
/>
<Education
levelOfEducation={levelOfEducation}
visibilityLevelOfEducation={visibilityLevelOfEducation}
formId="levelOfEducation"
{...commonFormProps}
/>
<SocialLinks
socialLinks={socialLinks}
draftSocialLinksByPlatform={draftSocialLinksByPlatform}
visibilitySocialLinks={visibilitySocialLinks}
formId="socialLinks"
{...commonFormProps}
/>
{isNameBlockVisible && (
<Name
name={name}
visibilityName={visibilityName}
formId="name"
{...commonFormProps}
/>
)}
{isLocationBlockVisible && (
<Country
country={country}
visibilityCountry={visibilityCountry}
formId="country"
{...commonFormProps}
/>
)}
{isLanguageBlockVisible && (
<PreferredLanguage
languageProficiencies={languageProficiencies}
visibilityLanguageProficiencies={visibilityLanguageProficiencies}
formId="languageProficiencies"
{...commonFormProps}
/>
)}
{isEducationBlockVisible && (
<Education
levelOfEducation={levelOfEducation}
visibilityLevelOfEducation={visibilityLevelOfEducation}
formId="levelOfEducation"
{...commonFormProps}
/>
)}
{isSocialLinksBLockVisible && (
<SocialLinks
socialLinks={socialLinks}
draftSocialLinksByPlatform={draftSocialLinksByPlatform}
visibilitySocialLinks={visibilitySocialLinks}
formId="socialLinks"
{...commonFormProps}
/>
)}
</div>
<div className="pt-md-3 col-md-8 col-lg-7 offset-lg-1">
{!this.isYOBDisabled() && this.renderAgeMessage()}
<Bio
bio={bio}
visibilityBio={visibilityBio}
formId="bio"
{...commonFormProps}
/>
{isBioBlockVisible && (
<Bio
bio={bio}
visibilityBio={visibilityBio}
formId="bio"
{...commonFormProps}
/>
)}
{getConfig().ENABLE_SKILLS_BUILDER_PROFILE && (
<LearningGoal
learningGoal={learningGoal}
@@ -278,11 +302,13 @@ class ProfilePage extends React.Component {
{...commonFormProps}
/>
)}
<Certificates
visibilityCourseCertificates={visibilityCourseCertificates}
formId="certificates"
{...commonFormProps}
/>
{isCertificatesBlockVisible && (
<Certificates
visibilityCourseCertificates={visibilityCourseCertificates}
formId="certificates"
{...commonFormProps}
/>
)}
</div>
</div>
</div>
@@ -348,7 +374,7 @@ ProfilePage.propTypes = {
// Learning Goal form data
learningGoal: PropTypes.string,
visibilityLearningGoal: PropTypes.string.isRequired,
visibilityLearningGoal: PropTypes.string,
// Other data we need
profileImage: PropTypes.shape({
@@ -397,6 +423,7 @@ ProfilePage.defaultProps = {
courseCertificates: null,
requiresParentalConsent: null,
dateJoined: null,
visibilityLearningGoal: null,
};
export default connect(

View File

@@ -114,16 +114,34 @@ describe('<ProfilePage />', () => {
expect(tree).toMatchSnapshot();
});
it('viewing other profile', () => {
it('viewing other profile with all fields', () => {
const contextValue = {
authenticatedUser: { userId: 123, username: 'staff', administrator: true },
config: getConfig(),
};
const component = (
<ProfilePageWrapper
contextValue={contextValue}
store={mockStore(storeMocks.viewOtherProfile)}
params={{ username: 'verified' }} // Override default params
store={mockStore({
...storeMocks.viewOtherProfile,
profilePage: {
...storeMocks.viewOtherProfile.profilePage,
account: {
...storeMocks.viewOtherProfile.profilePage.account,
name: 'user',
country: 'EN',
bio: 'bio',
courseCertificates: ['course certificates'],
levelOfEducation: 'some level',
languageProficiencies: ['some lang'],
socialLinks: ['twitter'],
timeZone: 'time zone',
accountPrivacy: 'all_users',
},
},
})}
match={{ params: { username: 'verified' } }} // Override default match
/>
);
const tree = renderer.create(component).toJSON();

View File

@@ -5,7 +5,7 @@ exports[`<ProfilePage /> Renders correctly in various states app loading 1`] = `
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div>
<div
@@ -36,7 +36,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -2307,7 +2307,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -2419,7 +2419,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -3484,7 +3484,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -3596,7 +3596,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -5536,7 +5536,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -5643,12 +5643,12 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
</div>
`;
exports[`<ProfilePage /> Renders correctly in various states viewing other profile 1`] = `
exports[`<ProfilePage /> Renders correctly in various states viewing other profile with all fields 1`] = `
<div
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -5704,7 +5704,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
<h1
className="h2 mb-0 font-weight-bold text-truncate"
>
verified
staff
</h1>
<p
className="mb-0"
@@ -5747,7 +5747,52 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
</div>
<div
className="d-none d-md-block float-right"
/>
>
<a
className="pgn__hyperlink default-link standalone-link btn btn-primary"
href="http://localhost:18150/records"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
View My Records
<span
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
Object {
"height": "1em",
"width": "1em",
}
}
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
</div>
</div>
</div>
<div
@@ -5765,7 +5810,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
<h1
className="h2 mb-0 font-weight-bold text-truncate"
>
verified
staff
</h1>
<p
className="mb-0"
@@ -5808,7 +5853,52 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
</div>
<div
className="d-md-none mb-4"
/>
>
<a
className="pgn__hyperlink default-link standalone-link btn btn-primary"
href="http://localhost:18150/records"
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
View My Records
<span
className="pgn__hyperlink__external-icon"
title="Opens in a new tab"
>
<span
className="pgn__icon"
style={
Object {
"height": "1em",
"width": "1em",
}
}
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
fill="currentColor"
/>
</svg>
<span
className="sr-only"
>
in a new tab
</span>
</span>
</span>
</a>
</div>
<div
className="pgn-transition-replace-group position-relative mb-5"
style={
@@ -5816,7 +5906,32 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
Full Name
</h2>
</div>
<p
className="h5"
data-hj-suppress={true}
>
user
</p>
</div>
</div>
<div
className="pgn-transition-replace-group position-relative mb-5"
style={
@@ -5824,7 +5939,30 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
Location
</h2>
</div>
<p
className="h5"
data-hj-suppress={true}
/>
</div>
</div>
<div
className="pgn-transition-replace-group position-relative mb-5"
style={
@@ -5832,7 +5970,30 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
Primary Language Spoken
</h2>
</div>
<p
className="h5"
data-hj-suppress={true}
/>
</div>
</div>
<div
className="pgn-transition-replace-group position-relative mb-5"
style={
@@ -5840,7 +6001,32 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
Education
</h2>
</div>
<p
className="h5"
data-hj-suppress={true}
>
Other education
</p>
</div>
</div>
<div
className="pgn-transition-replace-group position-relative mb-5"
style={
@@ -5848,7 +6034,29 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
Social Links
</h2>
</div>
<ul
className="list-unstyled"
/>
</div>
</div>
</div>
<div
className="pt-md-3 col-md-8 col-lg-7 offset-lg-1"
@@ -5860,7 +6068,32 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
About Me
</h2>
</div>
<p
className="lead"
data-hj-suppress={true}
>
bio
</p>
</div>
</div>
<div
className="pgn-transition-replace-group position-relative mb-4"
style={
@@ -5868,7 +6101,27 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
"height": null,
}
}
/>
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="editable-item-header mb-2"
>
<h2
className="edit-section-header"
id={null}
>
My Certificates
</h2>
</div>
You don't have any certificates yet.
</div>
</div>
</div>
</div>
</div>
@@ -5880,7 +6133,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -6818,7 +7071,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -6930,7 +7183,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -7935,7 +8188,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -8047,7 +8300,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -9060,7 +9313,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -9172,7 +9425,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -10020,7 +10273,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
className="row align-items-stretch"
>
<div
className="col col-sm-6 d-flex align-items-stretch"
className="col-12 col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"

View File

@@ -66,6 +66,25 @@ export function* handleFetchProfile(action) {
} else {
[account, courseCertificates] = result;
}
// Set initial visibility values for account
// Set account_privacy as custom is necessary so that when viewing another user's profile,
// their full name is displayed and change visibility forms are worked correctly
if (isAuthenticatedUserProfile && result[0].accountPrivacy === 'all_users') {
yield call(ProfileApiService.patchPreferences, action.payload.username, {
account_privacy: 'custom',
'visibility.name': 'all_users',
'visibility.bio': 'all_users',
'visibility.course_certificates': 'all_users',
'visibility.country': 'all_users',
'visibility.date_joined': 'all_users',
'visibility.level_of_education': 'all_users',
'visibility.language_proficiencies': 'all_users',
'visibility.social_links': 'all_users',
'visibility.time_zone': 'all_users',
});
}
yield put(fetchProfileSuccess(
account,
preferences,

View File

@@ -35,9 +35,12 @@ export const editableFormModeSelector = createSelector(
// or is being hidden from us (for other users' profiles)
let propExists = account[formId] != null && account[formId].length > 0;
propExists = formId === 'certificates' ? certificates.length > 0 : propExists; // overwrite for certificates
// If this isn't the current user's profile or if
// If this isn't the current user's profile
if (!isAuthenticatedUserProfile) {
return 'static';
}
// the current user has no age set / under 13 ...
if (!isAuthenticatedUserProfile || account.requiresParentalConsent) {
if (account.requiresParentalConsent) {
// then there are only two options: static or nothing.
// We use 'null' as a return value because the consumers of
// getMode render nothing at all on a mode of null.
@@ -228,13 +231,13 @@ export const visibilitiesSelector = createSelector(
switch (accountPrivacy) {
case 'custom':
return {
visibilityBio: preferences.visibilityBio || 'private',
visibilityCourseCertificates: preferences.visibilityCourseCertificates || 'private',
visibilityCountry: preferences.visibilityCountry || 'private',
visibilityLevelOfEducation: preferences.visibilityLevelOfEducation || 'private',
visibilityLanguageProficiencies: preferences.visibilityLanguageProficiencies || 'private',
visibilityName: preferences.visibilityName || 'private',
visibilitySocialLinks: preferences.visibilitySocialLinks || 'private',
visibilityBio: preferences.visibilityBio || 'all_users',
visibilityCourseCertificates: preferences.visibilityCourseCertificates || 'all_users',
visibilityCountry: preferences.visibilityCountry || 'all_users',
visibilityLevelOfEducation: preferences.visibilityLevelOfEducation || 'all_users',
visibilityLanguageProficiencies: preferences.visibilityLanguageProficiencies || 'all_users',
visibilityName: preferences.visibilityName || 'all_users',
visibilitySocialLinks: preferences.visibilitySocialLinks || 'all_users',
};
case 'private':
return {

View File

@@ -68,7 +68,7 @@ class Certificates extends React.Component {
})();
return (
<div key={`${modifiedDate}-${courseId}`} className="col col-sm-6 d-flex align-items-stretch">
<div key={`${modifiedDate}-${courseId}`} className="col-12 col-sm-6 d-flex align-items-stretch">
<div className="card mb-4 certificate flex-grow-1">
<div
className="certificate-type-illustration"