Compare commits

..

9 Commits

Author SHA1 Message Date
Jason Wesson
d598cf1e4b fix: replace injected Plugins directory with dependency 2023-11-08 20:12:46 +00:00
Jason Wesson
d5949f55c2 Merge master into PluggablePOCFeature (#908)
---------

Co-authored-by: jsnwesson <jsnwesson@users.noreply.github.com>
2023-11-07 20:03:44 -08:00
Jason Wesson
cd28310937 fix: hyperlink in plugin page should redirect to user profile (#897)
* add user's full name to plugn page

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-10-24 13:04:55 -07:00
Jason Wesson
b75347ad06 Merge master into PluginPOCFeature (#896) 2023-10-24 09:51:12 -07:00
Chris Deery
bd931338d8 Merge pull request #885 from openedx/PluginPOC/addTag
feat: add little tag to identify POC
2023-10-19 12:08:44 -04:00
Chris Deery
f03d5afa0d fix: update snapshot 2023-10-19 15:58:39 +00:00
Chris Deery
910e17f75d feat: add little tag to identify POC 2023-10-19 15:34:04 +00:00
Jason Wesson
2fa5cadf22 fix: rebase pluginPOCFeature with master branch (#884)
* fix(deps): update dependency @edx/paragon to v20.46.3

* chore(deps): update commitlint monorepo to v17.8.0

* fix(deps): update dependency @edx/frontend-component-footer to v12.4.0

* chore: bump frontend-platform (#869)

* fix: Add ID attribute to the main content (#845)

* chore: update browserslist DB (#871)

* fix(deps): update dependency @edx/frontend-platform to v5.6.1 (#875)

* build(deps): bump @babel/traverse from 7.22.5 to 7.23.2 (#879)

* bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.5 to 7.23.2.

* fix(deps): update dependency core-js to v3.33.0 (#876)
2023-10-18 11:58:21 -07:00
Jason Wesson
bd8221997e Move changes from ProfilePluginPOC to aperture/PluginPOCFeature (#883)
* build: create profile plugin page
* build: add plugins folder to Profile
* build: wrap Profile Plugin Page with Plugin

Co-authored-by: Jason Wesson <jwesson@2u.com>
2023-10-18 11:43:43 -07:00
45 changed files with 7395 additions and 5964 deletions

View File

@@ -14,10 +14,10 @@ jobs:
- lint
- test
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- uses: actions/setup-node@v4
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VER }}
- run: make requirements

View File

@@ -3,7 +3,7 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: 'frontend-app-profile'
name: 'Profile'
description: 'This is a micro-frontend application responsible for the display and updating of user profiles.'
links:
- url: 'https://github.com/openedx/frontend-app-profile/blob/master/README.rst'
@@ -21,4 +21,4 @@ spec:
type: 'service'
lifecycle: 'production'
owner: 2U-aperture
# (Optional) An array of different components or resources.
# (Optional) An array of different components or resources.

11475
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,18 +29,19 @@
],
"dependencies": {
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "12.7.0",
"@edx/frontend-component-header": "4.11.0",
"@edx/frontend-platform": "6.2.0",
"@edx/paragon": "21.5.6",
"@edx/frontend-component-footer": "12.5.1",
"@edx/frontend-component-header": "4.8.0",
"@edx/frontend-platform": "5.6.1",
"@edx/frontend-plugin-framework": "openedx/frontend-plugin-framework#jwesson/install-plugins",
"@edx/paragon": "^20.44.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.2.0",
"@pact-foundation/pact": "^11.0.2",
"classnames": "2.5.1",
"core-js": "3.35.0",
"classnames": "2.3.2",
"core-js": "3.33.1",
"history": "5.3.0",
"lodash.camelcase": "4.3.0",
"lodash.get": "4.4.2",
@@ -49,16 +50,17 @@
"prop-types": "15.8.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-error-boundary": "^4.0.11",
"react-helmet": "6.1.0",
"react-redux": "7.2.9",
"react-router": "6.20.1",
"react-router-dom": "6.20.1",
"react-router": "6.16.0",
"react-router-dom": "6.16.0",
"redux": "4.2.1",
"redux-devtools-extension": "2.13.9",
"redux-logger": "3.0.6",
"redux-saga": "1.3.0",
"redux-saga": "1.2.3",
"redux-thunk": "2.4.2",
"regenerator-runtime": "0.14.1",
"regenerator-runtime": "0.14.0",
"reselect": "4.1.8",
"universal-cookie": "4.0.4"
},
@@ -66,10 +68,11 @@
"@commitlint/cli": "17.8.1",
"@commitlint/config-angular": "17.8.1",
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "13.0.14",
"@edx/frontend-build": "13.0.4",
"@edx/reactifex": "2.2.0",
"@testing-library/react": "12.1.5",
"@wojtekmaj/enzyme-adapter-react-17": "0.8.0",
"codecov": "3.8.3",
"enzyme": "3.11.0",
"glob": "10.3.10",
"react-test-renderer": "17.0.2",

93
plugins/Plugin.jsx Normal file
View File

@@ -0,0 +1,93 @@
'use client';
import React, {
useEffect, useMemo, useState,
} from 'react';
import PropTypes from 'prop-types';
import { ErrorBoundary } from 'react-error-boundary';
import { logError } from '@edx/frontend-platform/logging';
import {
dispatchMountedEvent, dispatchReadyEvent, dispatchUnmountedEvent, useHostEvent,
} from './data/hooks';
import { PLUGIN_RESIZE } from './data/constants';
// see example-plugin-app/src/PluginOne.jsx for example of customizing errorFallback
function errorFallbackDefault() {
return (
<div>
<h2>
Oops! An error occurred. Please refresh the screen to try again.
</h2>
</div>
);
}
// eslint-disable-next-line react/function-component-definition
export default function Plugin({
children, className, style, ready, errorFallbackProp,
}) {
const [dimensions, setDimensions] = useState({
width: null,
height: null,
});
const finalStyle = useMemo(() => ({
...dimensions,
...style,
}), [dimensions, style]);
const errorFallback = errorFallbackProp || errorFallbackDefault;
// Error logging function
// Need to confirm: When an error is caught here, the logging will be sent to the child MFE's logging service
const logErrorToService = (error, info) => {
logError(error, { stack: info.componentStack });
};
useHostEvent(PLUGIN_RESIZE, ({ payload }) => {
setDimensions({
width: payload.width,
height: payload.height,
});
});
useEffect(() => {
dispatchMountedEvent();
return () => {
dispatchUnmountedEvent();
};
}, []);
useEffect(() => {
if (ready) {
dispatchReadyEvent();
}
}, [ready]);
return (
<div className={className} style={finalStyle}>
<ErrorBoundary
FallbackComponent={errorFallback}
onError={logErrorToService}
>
{children}
</ErrorBoundary>
</div>
);
}
Plugin.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
errorFallbackProp: PropTypes.func,
ready: PropTypes.bool,
style: PropTypes.object, // eslint-disable-line
};
Plugin.defaultProps = {
className: null,
errorFallbackProp: null,
style: {},
ready: true,
};

View File

@@ -0,0 +1,42 @@
'use client';
import React from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import PluginContainerIframe from './PluginContainerIframe';
import {
IFRAME_PLUGIN,
} from './data/constants';
import { pluginConfigShape } from './data/shapes';
// eslint-disable-next-line react/function-component-definition
export default function PluginContainer({ config, ...props }) {
if (config === null) {
return null;
}
// this will allow for future plugin types to be inserted in the PluginErrorBoundary
let renderer = null;
switch (config.type) {
case IFRAME_PLUGIN:
renderer = (
<PluginContainerIframe config={config} {...props} />
);
break;
// istanbul ignore next: default isn't meaningful, just satisfying linter
default:
}
return (
renderer
);
}
PluginContainer.propTypes = {
config: pluginConfigShape,
};
PluginContainer.defaultProps = {
config: null,
};

View File

@@ -0,0 +1,99 @@
import React, {
useEffect, useState,
} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {
PLUGIN_MOUNTED,
PLUGIN_READY,
PLUGIN_RESIZE,
} from './data/constants';
import {
dispatchPluginEvent,
useElementSize,
usePluginEvent,
} from './data/hooks';
import { pluginConfigShape } from './data/shapes';
/**
* Feature policy for iframe, allowing access to certain courseware-related media.
*
* We must use the wildcard (*) origin for each feature, as courseware content
* may be embedded in external iframes. Notably, xblock-lti-consumer is a popular
* block that iframes external course content.
* This policy was selected in conference with the edX Security Working Group.
* Changes to it should be vetted by them (security@edx.org).
*/
export const IFRAME_FEATURE_POLICY = (
'fullscreen; microphone *; camera *; midi *; geolocation *; encrypted-media *'
);
// eslint-disable-next-line react/function-component-definition
export default function PluginContainerIframe({
config, fallback, className, ...props
}) {
const { url } = config;
const { title, scrolling } = props;
const [mounted, setMounted] = useState(false);
const [ready, setReady] = useState(false);
const [iframeRef, iframeElement, width, height] = useElementSize();
useEffect(() => {
if (mounted) {
dispatchPluginEvent(iframeElement, {
type: PLUGIN_RESIZE,
payload: {
width,
height,
},
}, url);
}
}, [iframeElement, mounted, width, height, url]);
usePluginEvent(iframeElement, PLUGIN_MOUNTED, () => {
setMounted(true);
});
usePluginEvent(iframeElement, PLUGIN_READY, () => {
setReady(true);
});
return (
<>
<iframe
ref={iframeRef}
title={title}
src={url}
allow={IFRAME_FEATURE_POLICY}
scrolling={scrolling}
referrerPolicy="origin" // The sent referrer will be limited to the origin of the referring page: its scheme, host, and port.
className={classNames(
'border border-0',
{ 'd-none': !ready },
className,
)}
{...props}
/>
{!ready && fallback}
</>
);
}
PluginContainerIframe.propTypes = {
config: pluginConfigShape,
fallback: PropTypes.node,
scrolling: PropTypes.oneOf(['auto', 'yes', 'no']),
title: PropTypes.string,
className: PropTypes.string,
};
PluginContainerIframe.defaultProps = {
config: null,
fallback: null,
scrolling: 'auto',
title: null,
className: null,
};

View File

@@ -0,0 +1,45 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
// eslint-disable-next-line import/no-extraneous-dependencies
import { FormattedMessage } from 'react-intl';
import { logError } from '@edx/frontend-platform/logging';
export default class PluginErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, { stack: info.componentStack });
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return (
<FormattedMessage
id="plugin.load.failure.text"
defaultMessage="This content failed to load."
description="error message when an unexpected error occurs"
/>
);
}
return this.props.children;
}
}
PluginErrorBoundary.propTypes = {
children: PropTypes.node,
};
PluginErrorBoundary.defaultProps = {
children: null,
};

75
plugins/PluginSlot.jsx Normal file
View File

@@ -0,0 +1,75 @@
/* eslint-disable no-unused-vars */
import React, { forwardRef } from 'react';
import classNames from 'classnames';
import { Spinner } from '@edx/paragon';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
// import { usePluginSlot } from './data/hooks';
import PluginContainer from './PluginContainer';
const PluginSlot = forwardRef(({
as, id, intl, pluginProps, children, ...props
}, ref) => {
/* the plugins below are obtained by the id passed into PluginSlot by the Host MFE. See example/src/PluginsPage.jsx
for an example of how PluginSlot is populated, and example/src/index.jsx for a dummy JS config that holds all plugins
*/
// const { plugins, keepDefault } = usePluginSlot(id);
const { fallback } = pluginProps;
// TODO: Add internationalization to the "Loading" text on the spinner.
let finalFallback = (
<div className={classNames(pluginProps.className, 'd-flex justify-content-center align-items-center')}>
<Spinner animation="border" screenReaderText="Loading" />
</div>
);
if (fallback !== undefined) {
finalFallback = fallback;
}
let finalChildren = [];
// if (plugins.length > 0) {
// if (keepDefault) {
// finalChildren.push(children);
// }
// plugins.forEach((pluginConfig) => {
// finalChildren.push(
// <PluginContainer
// key={pluginConfig.url}
// config={pluginConfig}
// fallback={finalFallback}
// {...pluginProps}
// />,
// );
// });
// } else {
finalChildren = children;
// }
return React.createElement(
as,
{
...props,
ref,
},
finalChildren,
);
});
export default injectIntl(PluginSlot);
PluginSlot.propTypes = {
as: PropTypes.elementType,
children: PropTypes.node,
id: PropTypes.string.isRequired,
intl: intlShape.isRequired,
pluginProps: PropTypes.object, // eslint-disable-line
};
PluginSlot.defaultProps = {
as: 'div',
children: null,
pluginProps: {},
};

View File

@@ -0,0 +1,8 @@
// TODO: We expect other plugin types to be added here, such as LTI_PLUGIN and BUILD_TIME_PLUGIN.
export const IFRAME_PLUGIN = 'IFRAME_PLUGIN'; // loads iframe at the URL, rather than loading a JS file.
// Plugin lifecycle events
export const PLUGIN_MOUNTED = 'PLUGIN_MOUNTED';
export const PLUGIN_READY = 'PLUGIN_READY';
export const PLUGIN_UNMOUNTED = 'PLUGIN_UNMOUNTED';
export const PLUGIN_RESIZE = 'PLUGIN_RESIZE';

96
plugins/data/hooks.js Normal file
View File

@@ -0,0 +1,96 @@
import {
useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from 'react';
import { PLUGIN_MOUNTED, PLUGIN_READY, PLUGIN_UNMOUNTED } from './constants';
export function useMessageEvent(srcWindow, type, callback) {
useLayoutEffect(() => {
const listener = (event) => {
// Filter messages to those from our source window.
if (event.source === srcWindow) {
if (event.data.type === type) {
callback({ type, payload: event.data.payload });
}
}
};
if (srcWindow !== null) {
global.addEventListener('message', listener);
}
return () => {
global.removeEventListener('message', listener);
};
}, [srcWindow, type, callback]);
}
export function useHostEvent(type, callback) {
useMessageEvent(global.parent, type, callback);
}
export function usePluginEvent(iframeElement, type, callback) {
const contentWindow = iframeElement ? iframeElement.contentWindow : null;
useMessageEvent(contentWindow, type, callback);
}
export function dispatchMessageEvent(targetWindow, message, targetOrigin) {
// Checking targetOrigin falsiness here since '', null or undefined would all be reasons not to
// try to post a message to the origin.
if (targetOrigin) {
targetWindow.postMessage(message, targetOrigin);
}
}
export function dispatchPluginEvent(iframeElement, message, targetOrigin) {
dispatchMessageEvent(iframeElement.contentWindow, message, targetOrigin);
}
export function dispatchHostEvent(message) {
dispatchMessageEvent(global.parent, message, global.document.referrer);
}
export function dispatchReadyEvent() {
dispatchHostEvent({ type: PLUGIN_READY });
}
export function dispatchMountedEvent() {
dispatchHostEvent({ type: PLUGIN_MOUNTED });
}
export function dispatchUnmountedEvent() {
dispatchHostEvent({ type: PLUGIN_UNMOUNTED });
}
export function useElementSize() {
const observerRef = useRef();
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [element, setElement] = useState(null);
const measuredRef = useCallback(_element => {
setElement(_element);
}, []);
useEffect(() => {
observerRef.current = new ResizeObserver(() => {
if (element) {
setDimensions({
width: element.clientWidth,
height: element.clientHeight,
});
setOffset({
x: element.offsetLeft,
y: element.offsetTop,
});
}
});
if (element) {
observerRef.current.observe(element);
}
}, [element]);
return useMemo(
() => ([measuredRef, element, dimensions.width, dimensions.height, offset.x, offset.y]),
[measuredRef, element, dimensions, offset],
);
}

10
plugins/data/shapes.js Normal file
View File

@@ -0,0 +1,10 @@
/* eslint-disable import/prefer-default-export */
import PropTypes from 'prop-types';
import { IFRAME_PLUGIN } from './constants';
export const pluginConfigShape = PropTypes.shape({
url: PropTypes.string.isRequired,
type: PropTypes.oneOf([IFRAME_PLUGIN]).isRequired,
// This is a place for us to put any generic props we want to pass to the component. We need it.
props: PropTypes.object, // eslint-disable-line react/forbid-prop-types
});

18
plugins/index.js Normal file
View File

@@ -0,0 +1,18 @@
// export {
// usePluginSlot,
// } from './data/hooks';
export {
default as Plugin,
} from './Plugin';
export {
default as PluginContainer,
} from './PluginContainer';
export {
default as PluginSlot,
} from './PluginSlot';
export {
IFRAME_PLUGIN,
} from './data/constants';
export {
default as PluginErrorBoundary,
} from './PluginErrorBoundary';

View File

@@ -3,10 +3,6 @@
"profile.age.details": "لمشاركة ملفك الشخصي مع بقية متعلمي {siteName}، يجب أن تؤكد أنك عمرك يفوق 13 عامًا.",
"profile.age.set.date": "ضيط تاريخ ميلادك",
"profile.datejoined.member.since": "عضو منذ {year}",
"profile.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجدّدًا.",
"profile.viewMyRecords": "عرض سجلّاتي",
"profile.loading": "يتم تحميل الملف الشخصي...",
"profile.username.description": "معلومات ملفك الشخصي تظهر لك فقط. وحده اسم المستخدم الخاص بك يظهر للآخرين على {siteName}.",
"profile.bio.empty": "إضافة نبذة قصيرة",
"profile.bio.about.me": "نبذة عنّي",
"profile.certificate.organization.label": "من طرف",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "المدرسة الابتدائية / الأساسية",
"profile.education.levels.none": "دون تعليم رسمي",
"profile.education.levels.o": "نوع آخر من التعليم",
"profile.editbutton.edit": "تعديل",
"profile.formcontrols.who.can.see": "من يستطيع رؤية هذا:",
"profile.formcontrols.button.cancel": "إلغاء",
"profile.formcontrols.button.save": "حفظ",
"profile.formcontrols.button.saving": "الحفظ جارٍ",
"profile.formcontrols.button.saved": "تم الحفظ",
"profile.visibility.who.just.me": "أنا فقط",
"profile.visibility.who.everyone": "جميع من على {siteName}",
"profile.learningGoal.learningGoal": "هدف التعلم",
"profile.learningGoal.options.start_career": "أريد أن أبدأ مسيرتي المهنية",
"profile.learningGoal.options.advance_career": "أريد أن ارتقي في مسيرتي المهنية",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "تغيير",
"profile.sociallinks.add": "إضافة {network}",
"profile.sociallinks.social.links": "روابط التواصل الاجتماعي",
"profile.editbutton.edit": "تعديل",
"profile.formcontrols.who.can.see": "من يستطيع رؤية هذا:",
"profile.formcontrols.button.cancel": "إلغاء",
"profile.formcontrols.button.save": "حفظ",
"profile.formcontrols.button.saving": "الحفظ جارٍ",
"profile.formcontrols.button.saved": "تم الحفظ",
"profile.visibility.who.just.me": "أنا فقط",
"profile.visibility.who.everyone": "جميع من على {siteName}"
"profile.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجدّدًا.",
"profile.viewMyRecords": "عرض سجلّاتي",
"profile.loading": "يتم تحميل الملف الشخصي...",
"profile.username.description": "معلومات ملفك الشخصي تظهر لك فقط. وحده اسم المستخدم الخاص بك يظهر للآخرين على {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}"
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "Um Ihr Profil mit anderen {siteName}-Lernern zu teilen, müssen Sie bestätigen, dass Sie über 13 Jahre alt sind.",
"profile.age.set.date": "Legen Sie Ihr Geburtsdatum fest",
"profile.datejoined.member.since": "Mitglied seit {year}",
"profile.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
"profile.viewMyRecords": "Meine Aufzeichnungen anzeigen",
"profile.loading": "Profil lädt...",
"profile.username.description": "Ihre Profilinformationen sind nur für Sie sichtbar. Nur Ihr Benutzername ist für andere auf {siteName} sichtbar.",
"profile.bio.empty": "Fügen Sie Ihre Kurzbiografie hinzu",
"profile.bio.about.me": "Über mich",
"profile.certificate.organization.label": "Von",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Grundschule",
"profile.education.levels.none": "Keinen Bildungsabschluss",
"profile.education.levels.o": "Sonstige Bildung",
"profile.editbutton.edit": "Bearbeiten",
"profile.formcontrols.who.can.see": "Wer kann das sehen:",
"profile.formcontrols.button.cancel": "Abbrechen",
"profile.formcontrols.button.save": "Speichern",
"profile.formcontrols.button.saving": "Speichert",
"profile.formcontrols.button.saved": "Gespeichert",
"profile.visibility.who.just.me": "Nur ich",
"profile.visibility.who.everyone": "Alle auf {siteName}",
"profile.learningGoal.learningGoal": "Lernziel",
"profile.learningGoal.options.start_career": "Ich möchte meine Karriere starten",
"profile.learningGoal.options.advance_career": "Ich möchte mich beruflich weiterentwickeln",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Ändern",
"profile.sociallinks.add": "{network} hinzufügen",
"profile.sociallinks.social.links": "Soziale Netzwerke",
"profile.editbutton.edit": "Bearbeiten",
"profile.formcontrols.who.can.see": "Wer kann das sehen:",
"profile.formcontrols.button.cancel": "Abbrechen",
"profile.formcontrols.button.save": "Speichern",
"profile.formcontrols.button.saving": "Speichert",
"profile.formcontrols.button.saved": "Gespeichert",
"profile.visibility.who.just.me": "Nur ich",
"profile.visibility.who.everyone": "Alle auf {siteName}"
"profile.notfound.message": "Die gesuchte Seite ist nicht verfügbar oder es liegt ein Fehler in der URL vor. Bitte überprüfen Sie die URL und versuchen Sie es erneut.",
"profile.viewMyRecords": "Meine Aufzeichnungen anzeigen",
"profile.loading": "Profil lädt...",
"profile.username.description": "Ihre Profilinformationen sind nur für Sie sichtbar. Nur Ihr Benutzername ist für andere auf {siteName} sichtbar."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "Para compartir el perfil con otros {siteName} estudiantes, debe confirmar que es mayor de 13 años.",
"profile.age.set.date": "Establece tu fecha de nacimiento",
"profile.datejoined.member.since": "Miembro desde {year}",
"profile.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, comprueba la URL y vuelve a intentarlo.",
"profile.viewMyRecords": "Ver mis registros",
"profile.loading": "Cargando perfil...",
"profile.username.description": "La información del perfil solo la visualiza usted. Solo el nombre de usuario es visible para los demás en {siteName}.",
"profile.bio.empty": "Añade una breve biografía",
"profile.bio.about.me": "Sobre Mí",
"profile.certificate.organization.label": "Desde",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Enseñanza primaria",
"profile.education.levels.none": "Ninguna educación formal",
"profile.education.levels.o": "Otra educación",
"profile.editbutton.edit": "Editar",
"profile.formcontrols.who.can.see": "Quién puede ver esto:",
"profile.formcontrols.button.cancel": "Cancelar",
"profile.formcontrols.button.save": "Guardar",
"profile.formcontrols.button.saving": "Guardando",
"profile.formcontrols.button.saved": "Guardado",
"profile.visibility.who.just.me": "Solo yo",
"profile.visibility.who.everyone": "Todos en {siteName}",
"profile.learningGoal.learningGoal": "Objetivo de aprendizaje",
"profile.learningGoal.options.start_career": "quiero empezar mi carrera",
"profile.learningGoal.options.advance_career": "Quiero avanzar en mi carrera",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Cambiar",
"profile.sociallinks.add": "Añade {network}",
"profile.sociallinks.social.links": "Enlaces De Redes Sociales",
"profile.editbutton.edit": "Editar",
"profile.formcontrols.who.can.see": "Quién puede ver esto:",
"profile.formcontrols.button.cancel": "Cancelar",
"profile.formcontrols.button.save": "Guardar",
"profile.formcontrols.button.saving": "Guardando",
"profile.formcontrols.button.saved": "Guardado",
"profile.visibility.who.just.me": "Solo yo",
"profile.visibility.who.everyone": "Todos en {siteName}"
"profile.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, comprueba la URL y vuelve a intentarlo.",
"profile.viewMyRecords": "Ver mis registros",
"profile.loading": "Cargando perfil...",
"profile.username.description": "La información del perfil solo la visualiza usted. Solo el nombre de usuario es visible para los demás en {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "برای اشتراک‌گذاری پرونده کاربری خود با سایر یادگیرندگان {siteName}، باید تأیید کنید که بیش از 13 سال سن دارید.",
"profile.age.set.date": "تنظیم تاریخ تولد",
"profile.datejoined.member.since": "عضو شده از {year}",
"profile.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی آن وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره امتحان کنید.",
"profile.viewMyRecords": "مشاهده سوابق من",
"profile.loading": "در حال بارگذاری پرونده کاربری...",
"profile.username.description": "اطلاعات پرونده کاربری فقط برای شما قابل مشاهده است. سایرین فقط نام کاربری شما را در {siteName} می‌توانند ببینند.",
"profile.bio.empty": "بیوگرافی کوتاهی اضافه کنید",
"profile.bio.about.me": "درباره من",
"profile.certificate.organization.label": "از",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "مدرسه ابتدایی",
"profile.education.levels.none": "بدون تحصیلات رسمی",
"profile.education.levels.o": "تحصیلات متفرقه",
"profile.editbutton.edit": " ویرایش",
"profile.formcontrols.who.can.see": "کسانی که می‌توانند این را ببینند:",
"profile.formcontrols.button.cancel": "لغو‌",
"profile.formcontrols.button.save": "ذخیره",
"profile.formcontrols.button.saving": "در حال ذخیره",
"profile.formcontrols.button.saved": "ذخیره شد",
"profile.visibility.who.just.me": "فقط من",
"profile.visibility.who.everyone": "هرکسی در {siteName}",
"profile.learningGoal.learningGoal": "هدف یادگیری",
"profile.learningGoal.options.start_career": "من می خواهم کارم را شروع کنم",
"profile.learningGoal.options.advance_career": "من می خواهم حرفه ام را ارتقا دهم",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "تغییر",
"profile.sociallinks.add": "افزودن {network}",
"profile.sociallinks.social.links": "پیوندهای رسانه اجتماعی",
"profile.editbutton.edit": " ویرایش",
"profile.formcontrols.who.can.see": "کسانی که می‌توانند این را ببینند:",
"profile.formcontrols.button.cancel": "لغو‌",
"profile.formcontrols.button.save": "ذخیره",
"profile.formcontrols.button.saving": "در حال ذخیره",
"profile.formcontrols.button.saved": "ذخیره شد",
"profile.visibility.who.just.me": "فقط من",
"profile.visibility.who.everyone": "هرکسی در {siteName}"
"profile.notfound.message": "صفحه مورد نظر شما در دسترس نیست یا خطایی در نشانی آن وجود دارد. لطفاً نشانی اینترنتی را بررسی کرده و دوباره امتحان کنید.",
"profile.viewMyRecords": "مشاهده سوابق من",
"profile.loading": "در حال بارگذاری پرونده کاربری...",
"profile.username.description": "اطلاعات پرونده کاربری فقط برای شما قابل مشاهده است. سایرین فقط نام کاربری شما را در {siteName} می‌توانند ببینند."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "Pour partager votre profil avec d'autres étudiants {siteName}, vous devez confirmer que vous avez plus de 13 ans.",
"profile.age.set.date": "Définissez votre date de naissance",
"profile.datejoined.member.since": "Membre depuis {year}",
"profile.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
"profile.viewMyRecords": "Voir mes succès",
"profile.loading": "Chargement du profil....",
"profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}.",
"profile.bio.empty": "Ajouter une courte biographie",
"profile.bio.about.me": "À propos de moi",
"profile.certificate.organization.label": "De",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Enseignement primaire",
"profile.education.levels.none": "Sans diplôme",
"profile.education.levels.o": "Autre niveau d'étude",
"profile.editbutton.edit": "Modifier",
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
"profile.formcontrols.button.cancel": "Annuler",
"profile.formcontrols.button.save": "Enregistrer",
"profile.formcontrols.button.saving": "Enregistrement",
"profile.formcontrols.button.saved": "Enregistré",
"profile.visibility.who.just.me": "Juste moi",
"profile.visibility.who.everyone": "Tout le monde sur {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Modifier",
"profile.sociallinks.add": "Ajouter {network}",
"profile.sociallinks.social.links": "Liens vers les réseaux sociaux",
"profile.editbutton.edit": "Modifier",
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
"profile.formcontrols.button.cancel": "Annuler",
"profile.formcontrols.button.save": "Enregistrer",
"profile.formcontrols.button.saving": "Enregistrement",
"profile.formcontrols.button.saved": "Enregistré",
"profile.visibility.who.just.me": "Juste moi",
"profile.visibility.who.everyone": "Tout le monde sur {siteName}"
"profile.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
"profile.viewMyRecords": "Voir mes succès",
"profile.loading": "Chargement du profil....",
"profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "Pour partager votre profil avec d'autres apprenants {siteName}, vous devez confirmer que vous avez plus de 13 ans.",
"profile.age.set.date": "Entrez votre date de naissance",
"profile.datejoined.member.since": "Membre depuis {year}",
"profile.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
"profile.viewMyRecords": "Afficher mes dossiers",
"profile.loading": "Chargement du profil...",
"profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}.",
"profile.bio.empty": "Ajouter une courte biographie",
"profile.bio.about.me": "À propos de moi",
"profile.certificate.organization.label": "De",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Enseignement primaire",
"profile.education.levels.none": "Sans formation formelle",
"profile.education.levels.o": "Autre niveau de formation",
"profile.editbutton.edit": "Éditer",
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
"profile.formcontrols.button.cancel": "Annuler",
"profile.formcontrols.button.save": "Sauvegarder",
"profile.formcontrols.button.saving": "Sauvegarde en cours",
"profile.formcontrols.button.saved": "Sauvegardé",
"profile.visibility.who.just.me": "Juste moi",
"profile.visibility.who.everyone": "Tout le monde sur {siteName}",
"profile.learningGoal.learningGoal": "Objectif d'apprentissage",
"profile.learningGoal.options.start_career": "Je veux commencer ma carrière",
"profile.learningGoal.options.advance_career": "Je veux faire progresser ma carrière",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Modifier",
"profile.sociallinks.add": "Ajouter {network}",
"profile.sociallinks.social.links": "Liens vers les réseaux sociaux",
"profile.editbutton.edit": "Éditer",
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
"profile.formcontrols.button.cancel": "Annuler",
"profile.formcontrols.button.save": "Sauvegarder",
"profile.formcontrols.button.saving": "Sauvegarde en cours",
"profile.formcontrols.button.saved": "Sauvegardé",
"profile.visibility.who.just.me": "Juste moi",
"profile.visibility.who.everyone": "Tout le monde sur {siteName}"
"profile.notfound.message": "La page que vous recherchez n'est pas disponible ou il y a une erreur dans l'URL. Veuillez vérifier l'URL et réessayer.",
"profile.viewMyRecords": "Afficher mes dossiers",
"profile.loading": "Chargement du profil...",
"profile.username.description": "Les informations de votre profil ne sont visibles que par vous. Seul votre nom d'utilisateur est visible par les autres sur {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}"
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}"
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "Per condividere il tuo profilo con altri studenti di {siteName}, devi confermare di avere più di 13 anni.",
"profile.age.set.date": "Imposta la data di nascita ",
"profile.datejoined.member.since": "Membro da {year}",
"profile.notfound.message": "La pagina ricercata non è disponibile oppure è presente un errore nell'URL. Controllare l'URL e riprovare. ",
"profile.viewMyRecords": "Visualizza record personali ",
"profile.loading": "Caricamento del profilo... ",
"profile.username.description": "Le informazioni del tuo profilo sono visibili solo a te. Solo il tuo nome utente è visibile agli altri su {siteName}.",
"profile.bio.empty": "Aggiungi una breve biografia ",
"profile.bio.about.me": "Su di me",
"profile.certificate.organization.label": "Da ",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Scuola Primaria/Elementare",
"profile.education.levels.none": "Nessun livello educativo formale",
"profile.education.levels.o": "Altro livello educativo",
"profile.editbutton.edit": "Modifica",
"profile.formcontrols.who.can.see": "Chi può visualizzare: ",
"profile.formcontrols.button.cancel": "Annulla",
"profile.formcontrols.button.save": "Salva",
"profile.formcontrols.button.saving": "Salvataggio in corso",
"profile.formcontrols.button.saved": "Salvato",
"profile.visibility.who.just.me": "Solo io ",
"profile.visibility.who.everyone": "Tutti su {siteName}",
"profile.learningGoal.learningGoal": "Obiettivo di apprendimento",
"profile.learningGoal.options.start_career": "Voglio iniziare il mio percorso",
"profile.learningGoal.options.advance_career": "Voglio avanzare nel mio percorso",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Cambia",
"profile.sociallinks.add": "Aggiungi {network}",
"profile.sociallinks.social.links": "Link social ",
"profile.editbutton.edit": "Modifica",
"profile.formcontrols.who.can.see": "Chi può visualizzare: ",
"profile.formcontrols.button.cancel": "Annulla",
"profile.formcontrols.button.save": "Salva",
"profile.formcontrols.button.saving": "Salvataggio in corso",
"profile.formcontrols.button.saved": "Salvato",
"profile.visibility.who.just.me": "Solo io ",
"profile.visibility.who.everyone": "Tutti su {siteName}"
"profile.notfound.message": "La pagina ricercata non è disponibile oppure è presente un errore nell'URL. Controllare l'URL e riprovare. ",
"profile.viewMyRecords": "Visualizza record personali ",
"profile.loading": "Caricamento del profilo... ",
"profile.username.description": "Le informazioni del tuo profilo sono visibili solo a te. Solo il tuo nome utente è visibile agli altri su {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}"
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "Para partilhar o seu perfil com outros estudantes da plataforma {siteName}, tem de confirmar que tem mais de 13 anos de idade.",
"profile.age.set.date": "Indique a sua data de nascimento",
"profile.datejoined.member.since": "Utilizador desde {year}",
"profile.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
"profile.viewMyRecords": "Ver os Meus Registos",
"profile.loading": "A carregar perfil...",
"profile.username.description": "As informações do seu perfil só são visíveis para si. Apenas o seu nome de utilizador é visível para outros em {siteName}.",
"profile.bio.empty": "Adicionar uma breve biografia",
"profile.bio.about.me": "Sobre Mim",
"profile.certificate.organization.label": "De",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Primária",
"profile.education.levels.none": "Sem estudos",
"profile.education.levels.o": "Outra formação",
"profile.editbutton.edit": "Editar",
"profile.formcontrols.who.can.see": "Quem pode ver isto:",
"profile.formcontrols.button.cancel": "Cancelar",
"profile.formcontrols.button.save": "Guardar",
"profile.formcontrols.button.saving": "A Guardar",
"profile.formcontrols.button.saved": "Guardado",
"profile.visibility.who.just.me": "Apenas eu",
"profile.visibility.who.everyone": "Toda a gente em {siteName}",
"profile.learningGoal.learningGoal": "Objectivo de aprendizagem",
"profile.learningGoal.options.start_career": "Quero começar a minha carreira",
"profile.learningGoal.options.advance_career": "Quero progredir na minha carreira",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Alterar",
"profile.sociallinks.add": "Adicionar {network}",
"profile.sociallinks.social.links": "Links de Redes Sociais",
"profile.editbutton.edit": "Editar",
"profile.formcontrols.who.can.see": "Quem pode ver isto:",
"profile.formcontrols.button.cancel": "Cancelar",
"profile.formcontrols.button.save": "Guardar",
"profile.formcontrols.button.saving": "A Guardar",
"profile.formcontrols.button.saved": "Guardado",
"profile.visibility.who.just.me": "Apenas eu",
"profile.visibility.who.everyone": "Toda a gente em {siteName}"
"profile.notfound.message": "A página que procura não está disponível ou há um erro no URL. Por favor, verifique o URL e tente novamente.",
"profile.viewMyRecords": "Ver os Meus Registos",
"profile.loading": "A carregar perfil...",
"profile.username.description": "As informações do seu perfil só são visíveis para si. Apenas o seu nome de utilizador é visível para outros em {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}"
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -3,10 +3,6 @@
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}.",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
@@ -30,6 +26,14 @@
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
@@ -46,12 +50,8 @@
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}"
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -1,57 +1,57 @@
{
"profile.page.title": "简介 | {siteName}",
"profile.age.details": "要与其他 {siteName} 学员分享您的个人资料,您必须确认您已年满 13 岁。",
"profile.age.set.date": "设置您的出生日期",
"profile.datejoined.member.since": "会员自{year}",
"profile.notfound.message": "您访问的地址不存在或有误。请检查URL后重新尝试访问。",
"profile.viewMyRecords": "查看我的记录",
"profile.loading": "简介文件加载中......",
"profile.username.description": "您的个人资料信息仅对您可见。 {siteName} 上的其他人只能看到您的用户名。",
"profile.bio.empty": "添加个人简历",
"profile.bio.about.me": "个人资料",
"profile.certificate.organization.label": "从",
"profile.certificate.completion.date.label": "在{date}前完成",
"profile.no.certificates": "你还没有任何证书。",
"profile.certificates.my.certificates": "我的证书",
"profile.certificates.view.certificate": "查看证书",
"profile.certificates.types.verified": "认证证书",
"profile.certificates.types.professional": "专业证书",
"profile.certificates.types.unknown": "证书",
"profile.country.label": "位置",
"profile.country.empty": "添加地址",
"profile.education.empty": "添加教育程度",
"profile.education.education": "教育程度",
"profile.education.levels.p": "博士",
"profile.education.levels.m": "硕士",
"profile.education.levels.b": "学士学位",
"profile.education.levels.a": "副学士学位",
"profile.education.levels.hs": "高中",
"profile.education.levels.jhs": "初中",
"profile.education.levels.el": "小学",
"profile.education.levels.none": "未受正规教育",
"profile.education.levels.o": "其他教育程度",
"profile.learningGoal.learningGoal": "学习目标",
"profile.learningGoal.options.start_career": "我想开始我的职业生涯",
"profile.learningGoal.options.advance_career": "我想提升我的事业",
"profile.learningGoal.options.learn_something_new": "我想学习新东西",
"profile.learningGoal.options.something_else": "别的东西",
"profile.name.full.name": "全名",
"profile.name.details": "用于显示在账户和证书上的姓名。",
"profile.name.empty": "添加名字",
"profile.preferredlanguage.empty": "添加语言",
"profile.preferredlanguage.label": "主要使用语言",
"profile.profileavatar.upload-button": "上传照片",
"profile.profileavatar.remove.button": "移除",
"profile.image.alt.attribute": "个人头像",
"profile.profileavatar.change-button": "更改",
"profile.sociallinks.add": "添加{network}",
"profile.sociallinks.social.links": "社交链接",
"profile.editbutton.edit": "编辑",
"profile.formcontrols.who.can.see": "对谁可见:",
"profile.formcontrols.button.cancel": "取消",
"profile.formcontrols.button.save": "保存",
"profile.formcontrols.button.saving": "正在保存",
"profile.formcontrols.button.saved": "已保存",
"profile.visibility.who.just.me": "仅自己",
"profile.visibility.who.everyone": "{siteName} 上的每个人"
"profile.page.title": "Profile | {siteName}",
"profile.age.details": "To share your profile with other {siteName} learners, you must confirm that you are over the age of 13.",
"profile.age.set.date": "Set your date of birth",
"profile.datejoined.member.since": "Member since {year}",
"profile.bio.empty": "Add a short bio",
"profile.bio.about.me": "About Me",
"profile.certificate.organization.label": "From",
"profile.certificate.completion.date.label": "Completed on {date}",
"profile.no.certificates": "You don't have any certificates yet.",
"profile.certificates.my.certificates": "My Certificates",
"profile.certificates.view.certificate": "View Certificate",
"profile.certificates.types.verified": "Verified Certificate",
"profile.certificates.types.professional": "Professional Certificate",
"profile.certificates.types.unknown": "Certificate",
"profile.country.label": "Location",
"profile.country.empty": "Add location",
"profile.education.empty": "Add education",
"profile.education.education": "Education",
"profile.education.levels.p": "Doctorate",
"profile.education.levels.m": "Master's or professional degree",
"profile.education.levels.b": "Bachelor's Degree",
"profile.education.levels.a": "Associate's degree",
"profile.education.levels.hs": "Secondary/high school",
"profile.education.levels.jhs": "Junior secondary/junior high/middle school",
"profile.education.levels.el": "Elementary/primary school",
"profile.education.levels.none": "No formal education",
"profile.education.levels.o": "Other education",
"profile.editbutton.edit": "Edit",
"profile.formcontrols.who.can.see": "Who can see this:",
"profile.formcontrols.button.cancel": "Cancel",
"profile.formcontrols.button.save": "Save",
"profile.formcontrols.button.saving": "Saving",
"profile.formcontrols.button.saved": "Saved",
"profile.visibility.who.just.me": "Just me",
"profile.visibility.who.everyone": "Everyone on {siteName}",
"profile.learningGoal.learningGoal": "Learning Goal",
"profile.learningGoal.options.start_career": "I want to start my career",
"profile.learningGoal.options.advance_career": "I want to advance my career",
"profile.learningGoal.options.learn_something_new": "I want to learn something new",
"profile.learningGoal.options.something_else": "Something else",
"profile.name.full.name": "Full Name",
"profile.name.details": "This is the name that appears in your account and on your certificates.",
"profile.name.empty": "Add name",
"profile.preferredlanguage.empty": "Add language",
"profile.preferredlanguage.label": "Primary Language Spoken",
"profile.profileavatar.upload-button": "Upload Photo",
"profile.profileavatar.remove.button": "Remove",
"profile.image.alt.attribute": "profile avatar",
"profile.profileavatar.change-button": "Change",
"profile.sociallinks.add": "Add {network}",
"profile.sociallinks.social.links": "Social Links",
"profile.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
"profile.viewMyRecords": "View My Records",
"profile.loading": "Profile loading...",
"profile.username.description": "Your profile information is only visible to you. Only your username is visible to others on {siteName}."
}

View File

@@ -15,6 +15,7 @@ import {
import React from 'react';
import ReactDOM from 'react-dom';
import { useLocation } from 'react-router-dom';
import Header from '@edx/frontend-component-header';
import Footer from '@edx/frontend-component-footer';
@@ -27,15 +28,25 @@ import Head from './head/Head';
import AppRoutes from './routes/AppRoutes';
const RenderFooter = () => {
const location = useLocation();
return location.pathname.includes('/plugin') ? null : <Footer />;
};
const RenderHeader = () => {
const location = useLocation();
return location.pathname.includes('/plugin') ? null : <Header />;
};
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider store={configureStore()}>
<Head />
<Header />
<RenderHeader />
<main id="main">
<AppRoutes />
</main>
<Footer />
<RenderFooter />
</AppProvider>,
document.getElementById('root'),
);

View File

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

View File

@@ -6,7 +6,6 @@ const DateJoined = ({ date }) => {
if (date == null) {
return null;
}
return (
<p className="mb-0">
<FormattedMessage

View File

@@ -126,7 +126,7 @@ class ProfilePage extends React.Component {
return (
<span data-hj-suppress>
<h1 className="h2 mb-0 font-weight-bold text-truncate">{this.props.params.username}</h1>
<h1 className="h2 mb-0 font-weight-bold">{this.props.params.username}</h1>
<DateJoined date={dateJoined} />
{this.isYOBDisabled() && <UsernameDescription />}
<hr className="d-none d-md-block" />
@@ -178,7 +178,6 @@ class ProfilePage extends React.Component {
visibilityLearningGoal,
languageProficiencies,
visibilityLanguageProficiencies,
courseCertificates,
visibilityCourseCertificates,
bio,
visibilityBio,
@@ -197,17 +196,6 @@ 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">
@@ -224,7 +212,8 @@ class ProfilePage extends React.Component {
/>
</div>
</div>
<div className="col">
<div>PluginPOC</div>
<div className="col pl-0">
<div className="d-md-none">
{this.renderHeadingLockup()}
</div>
@@ -242,58 +231,46 @@ class ProfilePage extends React.Component {
<div className="d-md-none mb-4">
{this.renderViewMyRecordsButton()}
</div>
{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}
/>
)}
<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}
/>
</div>
<div className="pt-md-3 col-md-8 col-lg-7 offset-lg-1">
{!this.isYOBDisabled() && this.renderAgeMessage()}
{isBioBlockVisible && (
<Bio
bio={bio}
visibilityBio={visibilityBio}
formId="bio"
{...commonFormProps}
/>
)}
<Bio
bio={bio}
visibilityBio={visibilityBio}
formId="bio"
{...commonFormProps}
/>
{getConfig().ENABLE_SKILLS_BUILDER_PROFILE && (
<LearningGoal
learningGoal={learningGoal}
@@ -302,13 +279,11 @@ class ProfilePage extends React.Component {
{...commonFormProps}
/>
)}
{isCertificatesBlockVisible && (
<Certificates
visibilityCourseCertificates={visibilityCourseCertificates}
formId="certificates"
{...commonFormProps}
/>
)}
<Certificates
visibilityCourseCertificates={visibilityCourseCertificates}
formId="certificates"
{...commonFormProps}
/>
</div>
</div>
</div>

View File

@@ -114,34 +114,16 @@ describe('<ProfilePage />', () => {
expect(tree).toMatchSnapshot();
});
it('viewing other profile with all fields', () => {
it('viewing other profile', () => {
const contextValue = {
authenticatedUser: { userId: 123, username: 'staff', administrator: true },
config: getConfig(),
};
const component = (
<ProfilePageWrapper
contextValue={contextValue}
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
store={mockStore(storeMocks.viewOtherProfile)}
params={{ username: 'verified' }} // Override default params
/>
);
const tree = renderer.create(component).toJSON();

View File

@@ -0,0 +1,219 @@
/* eslint-disable react/prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { ensureConfig } from '@edx/frontend-platform';
import { AppContext } from '@edx/frontend-platform/react';
import { injectIntl, intlShape, FormattedDate } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTwitter, faFacebook, faLinkedin } from '@fortawesome/free-brands-svg-icons';
import {
ActionRow, Avatar, Card, Hyperlink, Icon,
} from '@edx/paragon';
import { HistoryEdu, VerifiedUser } from '@edx/paragon/icons';
import get from 'lodash.get';
import { Plugin } from '@edx/frontend-plugin-framework/src/plugins';
import PluginCountry from './forms/PluginCountry';
// Actions
import {
fetchProfile,
} from './data/actions';
// Components
import PageLoading from './PageLoading';
// Selectors
import { profilePageSelector } from './data/selectors';
// i18n
import messages from './ProfilePage.messages';
import eduMessages from './forms/Education.messages';
import withParams from '../utils/hoc';
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
// eslint-disable-next-line react/function-component-definition
function Fallback() {
return (
<div>this is broken as all get</div>
);
}
const platformDisplayInfo = {
facebook: {
icon: faFacebook,
name: '',
},
twitter: {
icon: faTwitter,
name: '',
},
linkedin: {
icon: faLinkedin,
name: '',
},
};
class ProfilePluginPage extends React.Component {
componentDidMount() {
this.props.fetchProfile(this.props.params.username);
}
renderContent() {
const {
profileImage,
country,
levelOfEducation,
socialLinks,
isLoadingProfile,
dateJoined,
name,
intl,
} = this.props;
if (isLoadingProfile) {
return <PageLoading srMessage={this.props.intl.formatMessage(messages['profile.loading'])} />;
}
return (
<Plugin fallbackComponent={<Fallback />}>
<Card className="mb-2">
<Card.Header
className="pb-5"
subtitle={(
<Hyperlink destination={`/u/${this.props.params.username}`}>
View public profile
</Hyperlink>
)}
actions={
(
<ActionRow className="mt-3">
{socialLinks
.filter(({ socialLink }) => Boolean(socialLink))
.map(({ platform, socialLink }) => (
<StaticListItem
key={platform}
name={platformDisplayInfo[platform].name}
url={socialLink}
platform={platform}
/>
))}
</ActionRow>
)
}
/>
<Card.Section className="text-center" muted>
<Avatar
size="xl"
className="profile-plugin-avatar"
src={profileImage.src}
alt="Profile image"
/>
<p className="h2 mb-0 font-weight-bold">{name}</p>
<p className="h3 mb-0 font-weight-bold">{this.props.params.username}</p>
<PluginCountry
country={country}
/>
</Card.Section>
<Card.Footer className="p-0">
<Card.Section className="pgn-icons-cell-vertical">
<Icon src={VerifiedUser} />
<p>
since <FormattedDate value={new Date(dateJoined)} year="numeric" />
</p>
</Card.Section>
<Card.Section className="pgn-icons-cell-vertical">
<Icon src={HistoryEdu} />
<p>
{intl.formatMessage(get(
eduMessages,
`profile.education.levels.${levelOfEducation}`,
eduMessages['profile.education.levels.o'],
))}
</p>
</Card.Section>
</Card.Footer>
</Card>
</Plugin>
);
}
render() {
return (
<div className="profile-page">
{this.renderContent()}
</div>
);
}
}
const SocialLink = ({ url, name, platform }) => (
<a href={url} className="font-weight-bold">
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
{name}
</a>
);
const StaticListItem = ({ url, name, platform }) => (
<ul className="list-inline">
<SocialLink name={name} url={url} platform={platform} />
</ul>
);
ProfilePluginPage.contextType = AppContext;
ProfilePluginPage.propTypes = {
// Account data
dateJoined: PropTypes.string,
// Country form data
country: PropTypes.string,
// Education form data
levelOfEducation: PropTypes.string,
// Social links form data
socialLinks: PropTypes.arrayOf(PropTypes.shape({
platform: PropTypes.string,
socialLink: PropTypes.string,
})),
// Other data we need
profileImage: PropTypes.shape({
src: PropTypes.string,
isDefault: PropTypes.bool,
}),
isLoadingProfile: PropTypes.bool.isRequired,
// Actions
fetchProfile: PropTypes.func.isRequired,
// Router
params: PropTypes.shape({
username: PropTypes.string.isRequired,
}).isRequired,
// i18n
intl: intlShape.isRequired,
};
ProfilePluginPage.defaultProps = {
profileImage: {},
levelOfEducation: null,
country: null,
socialLinks: [],
dateJoined: null,
};
export default connect(
profilePageSelector,
{
fetchProfile,
},
)(injectIntl(withParams(ProfilePluginPage)));

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-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none 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-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -103,8 +103,11 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -113,7 +116,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -145,7 +148,6 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -192,7 +194,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -224,7 +226,6 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -1698,7 +1699,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
</svg>
</span>
<select
className="d-inline-block form-control"
className="d-inline-block w-auto form-control"
id="visibilityCountry"
name="visibilityCountry"
onChange={[Function]}
@@ -2309,7 +2310,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -2373,7 +2374,6 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -2422,7 +2422,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -2489,8 +2489,11 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -2499,7 +2502,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -2531,7 +2534,6 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -2578,7 +2580,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -2610,7 +2612,6 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -3058,7 +3059,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
</svg>
</span>
<select
className="d-inline-block form-control"
className="d-inline-block w-auto form-control"
id="visibilityLevelOfEducation"
name="visibilityLevelOfEducation"
onChange={[Function]}
@@ -3489,7 +3490,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -3553,7 +3554,6 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -3602,7 +3602,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -3669,8 +3669,11 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -3679,7 +3682,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -3711,7 +3714,6 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -3758,7 +3760,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -3790,7 +3792,6 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -5023,7 +5024,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
</svg>
</span>
<select
className="d-inline-block form-control"
className="d-inline-block w-auto form-control"
id="visibilityLanguageProficiencies"
name="visibilityLanguageProficiencies"
onChange={[Function]}
@@ -5544,7 +5545,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -5608,7 +5609,6 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -5652,12 +5652,12 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
</div>
`;
exports[`<ProfilePage /> Renders correctly in various states viewing other profile with all fields 1`] = `
exports[`<ProfilePage /> Renders correctly in various states viewing other profile 1`] = `
<div
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -5701,8 +5701,11 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -5711,9 +5714,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
verified
</h1>
<p
className="mb-0"
@@ -5756,53 +5759,7 @@ 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"
data-testid="hyperlink-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
@@ -5818,9 +5775,9 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
verified
</h1>
<p
className="mb-0"
@@ -5863,53 +5820,7 @@ 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"
data-testid="hyperlink-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={
@@ -5917,32 +5828,7 @@ 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={
@@ -5950,30 +5836,7 @@ 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={
@@ -5981,30 +5844,7 @@ 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={
@@ -6012,32 +5852,7 @@ 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={
@@ -6045,29 +5860,7 @@ 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"
@@ -6079,32 +5872,7 @@ 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={
@@ -6112,27 +5880,7 @@ 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>
@@ -6144,7 +5892,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -6211,8 +5959,11 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -6221,7 +5972,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -6253,7 +6004,6 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -6300,7 +6050,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -6332,7 +6082,6 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -7084,7 +6833,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -7148,7 +6897,6 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -7197,7 +6945,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -7264,8 +7012,11 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -7274,7 +7025,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -7306,7 +7057,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -7353,7 +7103,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -7385,7 +7135,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -8042,7 +7791,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</svg>
</span>
<select
className="d-inline-block form-control"
className="d-inline-block w-auto form-control"
id="visibilityBio"
name="visibilityBio"
onChange={[Function]}
@@ -8204,7 +7953,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -8268,7 +8017,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -8317,7 +8065,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -8384,8 +8132,11 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -8394,7 +8145,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -8426,7 +8177,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -8473,7 +8223,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -8505,7 +8255,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -9170,7 +8919,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
</svg>
</span>
<select
className="d-inline-block form-control"
className="d-inline-block w-auto form-control"
id="visibilityBio"
name="visibilityBio"
onChange={[Function]}
@@ -9332,7 +9081,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -9396,7 +9145,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",
@@ -9445,7 +9193,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
className="profile-page"
>
<div
className="profile-page-bg-banner bg-primary d-md-block p-relative"
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
/>
<div
className="container-fluid"
@@ -9512,8 +9260,11 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
</div>
</div>
</div>
<div>
PluginPOC
</div>
<div
className="col"
className="col pl-0"
>
<div
className="d-md-none"
@@ -9522,7 +9273,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -9555,7 +9306,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
data-hj-suppress={true}
>
<h1
className="h2 mb-0 font-weight-bold text-truncate"
className="h2 mb-0 font-weight-bold"
>
staff
</h1>
@@ -10293,7 +10044,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
className="row align-items-stretch"
>
<div
className="col-12 col-sm-6 d-flex align-items-stretch"
className="col col-sm-6 d-flex align-items-stretch"
>
<div
className="card mb-4 certificate flex-grow-1"
@@ -10357,7 +10108,6 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
>
<span
className="pgn__icon"
data-testid="hyperlink-icon"
style={
Object {
"height": "1em",

View File

@@ -66,25 +66,6 @@ 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,12 +35,9 @@ 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
if (!isAuthenticatedUserProfile) {
return 'static';
}
// If this isn't the current user's profile or if
// the current user has no age set / under 13 ...
if (account.requiresParentalConsent) {
if (!isAuthenticatedUserProfile || 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.
@@ -231,13 +228,13 @@ export const visibilitiesSelector = createSelector(
switch (accountPrivacy) {
case 'custom':
return {
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',
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',
};
case 'private':
return {

View File

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

View File

@@ -0,0 +1,40 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Icon } from '@edx/paragon';
import { LocationOn } from '@edx/paragon/icons';
// Selectors
import { countrySelector } from '../data/selectors';
// eslint-disable-next-line react/prefer-stateless-function
class PluginCountry extends React.Component {
render() {
const {
country,
countryMessages,
} = this.props;
return (
<div className="pgn-icons-cell-horizontal">
<Icon src={LocationOn} />
<p className="h5 mt-1 ml-1">{countryMessages[country]}</p>
</div>
);
}
}
PluginCountry.propTypes = {
country: PropTypes.string,
countryMessages: PropTypes.objectOf(PropTypes.string).isRequired,
};
PluginCountry.defaultProps = {
country: null,
};
export default connect(
countrySelector,
{},
)(injectIntl(PluginCountry));

View File

@@ -119,7 +119,7 @@ exports[`<SocialLinks /> calls social links with edit mode bio 1`] = `
</svg>
</span>
<select
className="d-inline-block form-control"
className="d-inline-block w-auto form-control"
id="visibilitySocialLinks"
name="visibilitySocialLinks"
onChange={[Function]}

View File

@@ -39,7 +39,7 @@ const VisibilitySelect = ({ intl, className, ...props }) => {
<span className="d-inline-block ml-1 mr-2" style={{ width: '1.5rem' }}>
<FontAwesomeIcon icon={icon} />
</span>
<select className="d-inline-block form-control" {...props}>
<select className="d-inline-block w-auto form-control" {...props}>
<option key="private" value="private">
{intl.formatMessage(messages['profile.visibility.who.just.me'])}
</option>

View File

@@ -3,3 +3,4 @@ export { default as saga } from './data/sagas';
export { default as ProfilePage } from './ProfilePage';
export { default as NotFoundPage } from './NotFoundPage';
export { default as messages } from './ProfilePage.messages';
export { default as ProfilePluginPage } from './ProfilePluginPage';

View File

@@ -162,4 +162,28 @@
position: relative;
}
}
.pgn-icons-cell-vertical {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
margin: 1px;
}
.pgn-icons-cell-horizontal {
display: flex;
flex-direction: row;
justify-content: center;
margin: 1px;
}
.profile-plugin-avatar {
@include media-breakpoint-up(xs) {
max-width: 12rem;
margin-right: 0;
margin-top: -4rem;
margin-bottom: 1rem;
}
}
}

View File

@@ -4,11 +4,12 @@ import {
PageWrap,
} from '@edx/frontend-platform/react';
import { Routes, Route } from 'react-router-dom';
import { ProfilePage, NotFoundPage } from '../profile';
import { ProfilePage, NotFoundPage, ProfilePluginPage } from '../profile';
const AppRoutes = () => (
<Routes>
<Route path="/u/:username" element={<AuthenticatedPageRoute><ProfilePage /></AuthenticatedPageRoute>} />
<Route path="/u/:username/plugin" element={<AuthenticatedPageRoute><ProfilePluginPage /></AuthenticatedPageRoute>} />
<Route path="/notfound" element={<PageWrap><NotFoundPage /></PageWrap>} />
<Route path="*" element={<PageWrap><NotFoundPage /></PageWrap>} />
</Routes>

View File

@@ -15,6 +15,7 @@ jest.mock('@edx/frontend-platform/auth', () => ({
jest.mock('../profile', () => ({
ProfilePage: () => (<div>Profile page</div>),
NotFoundPage: () => (<div>Not found page</div>),
ProfilePluginPage: () => (<div>Plugin page</div>),
}));
const RoutesWithProvider = (context, path) => (
@@ -54,6 +55,22 @@ describe('routes', () => {
expect(screen.getByText('Profile page')).toBeTruthy();
});
test('Profile Plugin page should be accessible for authenticated users', () => {
render(
RoutesWithProvider(
{
authenticatedUser: {
username: 'edx',
email: 'edx@example.com',
},
config: getConfig(),
},
'/u/edx/plugin',
),
);
expect(screen.getByText('Plugin page')).toBeTruthy();
});
test('should show NotFound page for a bad route', () => {
render(
RoutesWithProvider(unauthenticatedUser, '/nonMatchingRoute'),