Compare commits

...

10 Commits

Author SHA1 Message Date
Ihor Romaniuk
72bce9e652 fix: save scroll position on exit from video xblock fullscreen mode (#981)
* fix: save scroll position on exit from video xblock fullscreen mode

* fix: change initial scroll top value

* fix: update tests
2023-06-21 13:45:34 -04:00
Eugene Dyudyunov
66482ab23a fix: first section celebration
Fix the first section celebration modal showing logic.

On Nutmeg+ it's shown only after the page reload or after going directly
to the second section from the course home. Going through the course
with the Next/Previous buttons has no effect (which worked on Maple).

Notes:
- the weekly goal has the same showing logic, but I assume that is
correct behavior so no changes are added for it in this commit.
- showing a celebration modal for the first section completion when
going directly to the first unit of the second section seems to be a bug
(reproduces on Maple too)
2023-02-14 15:40:50 -05:00
Ihor Romaniuk
03e0f41692 fix: fix alignment in the streak celebration modal (#974)
* fix: fix alignment in the streak celebration modal

* chore: update package-log
2022-12-05 11:54:48 -05:00
Diana Catalina Olarte
8e1904a235 fix: show site name instead of edX
(cherry picked from commit cafb881a61)
2022-05-18 15:52:10 +01:00
Michael Terry
88485a0f77 fix: add back es-check & fsevents for now to fix build
A previous commit (7f37575) dropped es-check, which dropped
fsevents, which caused our build system (which is still using
npm@6) to fail with an error like `Unsupported platform for
fsevents` when trying to install fsevents through a dependency
(e.g. when installing npm aliases).

I am reintroducing all the package-lock changes from that commit
to get back fsevents in a state where that error does not occur.

I think a longer-term fix would be to instead upgrade our build
system to node16 / npm6. But this is an easy fix for now to unblock
the builds.
2022-04-14 10:16:14 -04:00
edx-semantic-release
361a099ed1 chore(i18n): update translations 2022-04-10 17:08:59 -04:00
Adam Stankiewicz
7f3757539a build: use shared browserslist config and remove is-es5 check 2022-04-08 16:30:25 -04:00
Michael Terry
44f5132e2a fix: downgrade react and upgrade some other deps to align
Now that we are using node 16, peer dependencies are much more
strict about aligning between all of our dependencies.

This PR downgrades react from 17 to 16 (no changes) and upgrades
paragon and frontend-lib-special-exams to all be on the same
page about what peer dependency ranges are valid.
2022-04-08 16:00:29 -04:00
Renovate Bot
53b19c9be3 chore(deps): update codecov/codecov-action action to v3 2022-04-07 15:24:08 -04:00
edX requirements bot
abc374b60a chore!: Dropped support for Node 12 2022-04-07 15:15:04 -04:00
22 changed files with 919 additions and 8815 deletions

View File

@@ -2,7 +2,7 @@ name: validate
on:
push:
branches:
- 'master'
- master
pull_request:
branches:
- '**'
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [12, 14, 16]
node: [16]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
@@ -19,6 +19,6 @@ jobs:
node-version: ${{ matrix.node }}
- run: make validate.ci
- name: Upload coverage
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true

1
.husky/_/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*

31
.husky/_/husky.sh Normal file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
if [ -z "$husky_skip_init" ]; then
debug () {
if [ "$HUSKY_DEBUG" = "1" ]; then
echo "husky (debug) - $1"
fi
}
readonly hook_name="$(basename "$0")"
debug "starting $hook_name..."
if [ "$HUSKY" = "0" ]; then
debug "HUSKY env variable is set to 0, skipping hook"
exit 0
fi
if [ -f ~/.huskyrc ]; then
debug "sourcing ~/.huskyrc"
. ~/.huskyrc
fi
export readonly husky_skip_init=1
sh -e "$0" "$@"
exitCode="$?"
if [ $exitCode != 0 ]; then
echo "husky - $hook_name hook exited with code $exitCode (error)"
fi
exit $exitCode
fi

View File

@@ -58,7 +58,6 @@ validate:
npm run lint -- --max-warnings 0
npm run test
npm run build
npm run is-es5
.PHONY: validate.ci
validate.ci:

9521
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,13 +7,11 @@
"url": "git+https://github.com/edx/frontend-app-learning.git"
},
"browserslist": [
"last 2 versions",
"ie 11"
"extends @edx/browserslist-config"
],
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"is-es5": "es-check es5 ./dist/*.js",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"prepare": "husky install",
@@ -34,9 +32,9 @@
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.2.2",
"@edx/frontend-component-header": "2.4.6",
"@edx/frontend-lib-special-exams": "1.16.0",
"@edx/frontend-lib-special-exams": "1.16.3",
"@edx/frontend-platform": "1.15.6",
"@edx/paragon": "19.13.6",
"@edx/paragon": "19.14.1",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
@@ -49,8 +47,8 @@
"js-cookie": "3.0.1",
"lodash.camelcase": "4.3.0",
"prop-types": "15.8.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "6.1.0",
"react-redux": "7.2.8",
"react-router": "5.2.1",
@@ -63,6 +61,7 @@
"util": "0.12.4"
},
"devDependencies": {
"@edx/browserslist-config": "1.0.2",
"@edx/frontend-build": "9.1.4",
"@edx/reactifex": "1.1.0",
"@pact-foundation/pact": "9.17.3",

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import { useDispatch } from 'react-redux';
@@ -46,10 +46,8 @@ function Course({
// Below the tabs, above the breadcrumbs alerts (appearing in the order listed here)
const dispatch = useDispatch();
const celebrateFirstSection = celebrations && celebrations.firstSection;
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(shouldCelebrateOnSectionLoad(
courseId, sequenceId, celebrateFirstSection, dispatch, celebrations,
));
const [firstSectionCelebrationOpen, setFirstSectionCelebrationOpen] = useState(false);
// If streakLengthToCelebrate is populated, that modal takes precedence. Wait til the next load to display
// the weekly goal celebration modal.
const [weeklyGoalCelebrationOpen, setWeeklyGoalCelebrationOpen] = useState(
@@ -74,6 +72,17 @@ function Course({
/** [MM-P2P] Experiment */
const MMP2P = initCoursewareMMP2P(courseId, sequenceId, unitId);
useEffect(() => {
const celebrateFirstSection = celebrations && celebrations.firstSection;
setFirstSectionCelebrationOpen(shouldCelebrateOnSectionLoad(
courseId,
sequenceId,
celebrateFirstSection,
dispatch,
celebrations,
));
}, [sequenceId]);
return (
<SidebarProvider courseId={courseId} unitId={unitId}>
<Helmet>

View File

@@ -9,6 +9,7 @@ import {
useWindowSize,
} from '@edx/paragon';
import { useDispatch } from 'react-redux';
import ClapsMobile from './assets/claps_280x201.gif';
import ClapsTablet from './assets/claps_456x328.gif';
import messages from './messages';
@@ -19,12 +20,13 @@ import { useModel } from '../../../generic/model-store';
function CelebrationModal({
courseId, intl, isOpen, onClose, ...rest
}) {
const { org } = useModel('courseHomeMeta', courseId);
const { org, celebrations } = useModel('courseHomeMeta', courseId);
const dispatch = useDispatch();
const wideScreen = useWindowSize().width >= breakpoints.small.minWidth;
useEffect(() => {
if (isOpen) {
recordFirstSectionCelebration(org, courseId);
recordFirstSectionCelebration(org, courseId, celebrations, dispatch);
}
}, [isOpen]);

View File

@@ -15,9 +15,20 @@ function handleNextSectionCelebration(sequenceId, nextSequenceId) {
});
}
function recordFirstSectionCelebration(org, courseId) {
function recordFirstSectionCelebration(org, courseId, celebrations, dispatch) {
// Tell the LMS
postCelebrationComplete(courseId, { first_section: false });
// Update our local copy of course data from LMS
dispatch(updateModel({
modelType: 'courseHomeMeta',
model: {
id: courseId,
celebrations: {
...celebrations,
firstSection: false,
},
},
}));
// Tell our analytics
const { administrator } = getAuthenticatedUser();

View File

@@ -0,0 +1,15 @@
import { recordFirstSectionCelebration } from './utils';
jest.mock('@edx/frontend-platform/analytics');
jest.mock('./data/api');
jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedUser: jest.fn(() => ({ administrator: 'admin' })),
}));
describe('recordFirstSectionCelebration', () => {
it('updates the local copy of the course data from the LMS', async () => {
const dispatchMock = jest.fn();
recordFirstSectionCelebration('org', 'courseId', 'celebration', dispatchMock);
expect(dispatchMock).toHaveBeenCalled();
});
});

View File

@@ -96,6 +96,7 @@ function Unit({
const [showError, setShowError] = useState(false);
const [modalOptions, setModalOptions] = useState({ open: false });
const [shouldDisplayHonorCode, setShouldDisplayHonorCode] = useState(false);
const [windowTopOffset, setWindowTopOffset] = useState(null);
const unit = useModel('units', id);
const course = useModel('coursewareMeta', courseId);
@@ -123,6 +124,13 @@ function Unit({
} = data;
if (type === 'plugin.resize') {
setIframeHeight(payload.height);
// We observe exit from the video xblock full screen mode
// and do page scroll to the previously saved scroll position
if (windowTopOffset !== null) {
window.scrollTo(0, Number(windowTopOffset));
}
if (!hasLoaded && iframeHeight === 0 && payload.height > 0) {
setHasLoaded(true);
if (onLoaded) {
@@ -132,12 +140,16 @@ function Unit({
} else if (type === 'plugin.modal') {
payload.open = true;
setModalOptions(payload);
} else if (type === 'plugin.videoFullScreen') {
// We listen for this message from LMS to know when we need to
// save or reset scroll position on toggle video xblock full screen mode.
setWindowTopOffset(payload.open ? window.scrollY : null);
} else if (data.offset) {
// We listen for this message from LMS to know when the page needs to
// be scrolled to another location on the page.
window.scrollTo(0, data.offset + document.getElementById('unit-iframe').offsetTop);
}
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded]);
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded, setWindowTopOffset, windowTopOffset]);
useEventListener('message', receiveMessage);
useEffect(() => {
sendUrlHashToFrame(document.getElementById('unit-iframe'));

View File

@@ -131,6 +131,21 @@ describe('Unit', () => {
expect(window.scrollY === testMessageWithOffset.offset);
});
it('scrolls page on MessagaeEvent when receiving videoFullScreen state', async () => {
// Set message to constain video full screen data.
const defaultTopOffset = 800;
const testMessageWithOtherHeight = { ...messageEvent, payload: { height: 500 } };
const testMessageWithFullscreenState = (isOpen) => ({ type: 'plugin.videoFullScreen', payload: { open: isOpen } });
render(<Unit {...mockData} />);
Object.defineProperty(window, 'scrollY', { value: defaultTopOffset, writable: true });
window.postMessage(testMessageWithFullscreenState(true), '*');
window.postMessage(testMessageWithFullscreenState(false), '*');
window.postMessage(testMessageWithOtherHeight, '*');
await expect(waitFor(() => expect(window.scrollTo()).toHaveBeenCalledTimes(1)));
expect(window.scrollY === defaultTopOffset);
});
it('ignores MessageEvent with unhandled type', async () => {
// Clone message and set different type.
const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };

View File

@@ -98,9 +98,9 @@ export function SupportMissionBullet() {
<CheckmarkBullet />
<FormattedMessage
id="learning.generic.upsell.supportMissionBullet"
defaultMessage="Support our {missionInBoldText} at edX"
defaultMessage="Support our {missionInBoldText} at {siteName}"
description="Bullet encouraging user to support edX's goals."
values={{ missionInBoldText }}
values={{ missionInBoldText, siteName: getConfig().SITE_NAME }}
/>
</li>
);

View File

@@ -34,8 +34,8 @@
"learning.goals.unsubscribe.header": "قمت بالغاء اشتراكك في شعارات التذكيرية لاهداف",
"learning.goals.unsubscribe.loading": "يجري الغاء الاشتراك...",
"learning.goals.unsubscribe.errorDescription": "لم نستطع الغاء اشتراكك في اشعارات التذكيرية باهدافك على بريدك الالكتروني. رجاءا حاول مرة اخرى او {contactSupport} للمساعدة.",
"learning.outline.alert.cert.when": "تنتهي هذه الدورة التدريبية في {courseEndDateFormatted}. تمت جدولة الدرجات النهائية والشهادات\n لتكون متاحة بعد {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header": "ستكون درجتك وشهادتك جاهزة قريبًا!",
"learning.outline.alert.cert.earnedNotAvailable": "This course ends on {courseEndDateFormatted}. Final grades and any earned certificates are\n scheduled to be available after {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header.v2": "Your grade and certificate status will be available soon.",
"cert.alert.earned.ready.header": "ألف مبروك! شهادتك جاهزة.",
"cert.alert.notPassing.header": "أنت غير مؤهل بعد للحصول على شهادة",
"cert.alert.notPassing.button": "عرض الدرجات",
@@ -113,7 +113,7 @@
"learning.outline.sequence-due": "{description} في{assignmentDue}",
"progress.certificateStatus.unverifiedBody": "لإنشاء شهادة ، يجب عليك إكمال عملية التحقق من الهوية. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "اعرض إنجازاتك على لينكد ان أو على سيرتك الذاتية اليوم. يمكنك تنزيل شهادتك الآن والوصول إليها في أي وقت من لوحة التحكم والملف الشخصي.",
"courseCelebration.certificateBody.notAvailable.endDate": "تنتهي هذه الدورة التدريبية في {endDate} ومن المقرر أن تكون الدرجات النهائية والشهادات\n متاحة بعد {certAvailableDate}.",
"courseCelebration.certificateBody.notAvailable.endDate": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailabilityDate}.",
"progress.certificateStatus.notPassingHeader": "حالة الشهادة",
"progress.certificateStatus.notPassingBody": "من أجل التأهل للحصول على شهادة ، يجب أن تكون حاصلاً على درجة النجاح.",
"progress.certificateStatus.inProgressHeader": "المزيد من المحتوى قريبا!",
@@ -131,7 +131,7 @@
"progress.certificateStatus.upgradeHeader": "احصل على شهادة.",
"progress.certificateStatus.upgradeBody": "أنت في مسجل في المساق كمستمع ولست مؤهلاً للحصول على شهادة. من أجل الحصول على شهادة ، قم بترقية تسجيلك في المسافق اليوم.",
"progress.certificateStatus.upgradeButton": "الترقية الآن",
"progress.certificateStatus.unverifiedHomeHeader": "يجب التحقق من هويتك لتتمكن من الحصول على شهادة!",
"progress.certificateStatus.unverifiedHomeHeader.v2": "Verify your identity to qualify for a certificate.",
"progress.certificateStatus.unverifiedHomeButton": "تحقق من هويتي",
"progress.certificateStatus.unverifiedHomeBody": "من أجل إنشاء شهادة لهذه الدورة ، يجب عليك إكمال عملية التحقق من الهوية.",
"progress.completion.donut.label": "مكتمل",
@@ -262,6 +262,7 @@
"notes.button.hide": "إخفاء الملاحظات",
"courseExit.catalogSearchSuggestion": "هل تطمح إلى تعلّم المزيد؟{searchOurCatalogLink} لاستكشاف المزيد من المساقات والبرامج.",
"courseCelebration.certificateBody.available": "اعرض إنجازاتك على لينكد إن أو سيرتك الذاتية اليوم.\nيمكنك تنزيل الشهادة الآن والوصول إليها في أي وقت من\n{dashboardLink} و{profileLink}.",
"courseCelebration.certificateBody.notAvailable.endDate.v2": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailableDate}.",
"courseCelebration.certificateBody.unverified": "لإنشاء شهادة يجب عليك إتمام عملية التحقق من الهوية.\n{idVerificationSupportLink} الآن.",
"courseCelebration.certificateBody.upgradable": "لم يفت الأوان للترقية. بالنسبة لـ {price} ستقوم بإلغاء تأمين الوصول إلى كافة أنواع \nالواجبات في هذا المساق. عند الانتهاء، ستحصل على شهادة تم التحقق منها وهي إحدى\nالوثائق القيّمة لتحسين فرصك الوظيفية وتطويرك المهني، أو لتسليط الضوء على\nشهادة في التطبيقات التعليمية.",
"courseCelebration.upgradeDiscountCodePrompt": "استخدم الرمز {code} عند إتمام الطلب لخصم {percent}%!",
@@ -274,7 +275,7 @@
"courseCelebration.dashboardInfo": "يمكنك الوصول إلى هذا المساق ومواده على لوحة معلوماتك {dashboardLink}.",
"courseExit.programs.applyForCredit": "تقدم بطلب للحصول على ائتمان",
"courseCelebration.certificateHeader.downloadable": "!شهادتك جاهزة",
"courseCelebration.certificateHeader.notAvailable": "سيكون كل من درجتك وشهادتك جاهزين قريبًا!",
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate status will be available soon.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "إذا كنت قد حصلت على درجة النجاح ، فسيتم إصدار شهادتك تلقائيًا.",
"courseCelebration.certificateHeader.unverified": "يجب إكمال عملية التحقق للحصول على شهادتك",
"courseCelebration.certificateHeader.requestable": "تهانينا، لقد تأهلت للحصول على شهادة!",
@@ -396,7 +397,7 @@
"learning.generic.upsell.fullAccessBullet.fullAccess": "Full access",
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} to course content and materials, even after the course ends",
"learning.generic.upsell.supportMissionBullet.mission": "mission",
"learning.generic.upsell.supportMissionBullet": "ادعم {missionInBoldText} في edX",
"learning.generic.upsell.supportMissionBullet": "ادعم {missionInBoldText} في {siteName}",
"masquerade-widget.userName.error.generic": "حدث خطأ؛ يرجى المحاولة مرة أخرى.",
"masquerade-widget.userName.input.placeholder": "اسم المستخدم أو البريد الإلكتروني",
"masquerade-widget.userName.input.label": "عرف كهذا المستخدم",
@@ -408,7 +409,7 @@
"tours.button.okay": "تمام",
"tours.button.beginTour": "ابدأ الجولة",
"tours.button.launchTour": "انطلاق الجولة",
"tours.newUserModal.body": "Lets take a quick tour of edX so you can get the most out of your course.",
"tours.newUserModal.body": "Lets take a quick tour of {siteName} so you can get the most out of your course.",
"tours.newUserModal.title.welcome": "مرحبًا بك في",
"tours.button.skipForNow": "تخطي في الوقت الراهن",
"tours.datesCheckpoint.body": "Important dates can help you stay on track.",

View File

@@ -34,8 +34,8 @@
"learning.goals.unsubscribe.header": "Te has desinscrito de los recordatorios de objetivos.",
"learning.goals.unsubscribe.loading": "Desinscribiendo...",
"learning.goals.unsubscribe.errorDescription": "No fue posible desinscribirte de tus correos de recordatorios de objetivos. Por favor inténtalo más tarde o ponte en contacto con el equipo de soporte para solicitar ayuda {contactSupport}",
"learning.outline.alert.cert.when": "Este curso finaliza el {courseEndDateFormatted}. Las calificaciones finales y los certificados están\n programados para estar disponibles después de {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header": "Tu calificación y tu certificado estarán listos en breve.",
"learning.outline.alert.cert.earnedNotAvailable": "This course ends on {courseEndDateFormatted}. Final grades and any earned certificates are\n scheduled to be available after {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header.v2": "Your grade and certificate status will be available soon.",
"cert.alert.earned.ready.header": "¡Felicitaciones! Tu certificado está listo.",
"cert.alert.notPassing.header": "Aún no eres elegible para obtener un certificado",
"cert.alert.notPassing.button": "Ver calificaciones",
@@ -113,7 +113,7 @@
"learning.outline.sequence-due": "Fecha límite para {description}: {assignmentDue}",
"progress.certificateStatus.unverifiedBody": "Para generar un certificado, debes completar la verificación de identidad. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Muestra tu logro en LinkedIn o en tu currículum. Puedes descargar tu certificado ahora y acceder a él en cualquier momento desde tu panel de estudiante y tu perfil.",
"courseCelebration.certificateBody.notAvailable.endDate": "Este curso terminó el {endDate} y las calificaciones finales y los certificados están programados para estar\ndisponibles después de {certAvailableDate}.",
"courseCelebration.certificateBody.notAvailable.endDate": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailabilityDate}.",
"progress.certificateStatus.notPassingHeader": "Estado del certificado",
"progress.certificateStatus.notPassingBody": "Para poder obtener un certificado, es necesario tener una calificación de aprobado.",
"progress.certificateStatus.inProgressHeader": "¡Pronto habrá más contenido!",
@@ -131,7 +131,7 @@
"progress.certificateStatus.upgradeHeader": "Obtén un certificado",
"progress.certificateStatus.upgradeBody": "Estás en la opción auditada y no calificas para un certificado. Para poder obtener un certificado, cambiate a la opción verificada del curso hoy mismo.",
"progress.certificateStatus.upgradeButton": "Actualizar Ahora",
"progress.certificateStatus.unverifiedHomeHeader": "Verifica tu identidad para obtener un certificado.",
"progress.certificateStatus.unverifiedHomeHeader.v2": "Verify your identity to qualify for a certificate.",
"progress.certificateStatus.unverifiedHomeButton": "Verificar mi identidad",
"progress.certificateStatus.unverifiedHomeBody": "Para generar un certificado para este curso, debes completar el proceso de verificación de identidad.",
"progress.completion.donut.label": "Completado",
@@ -262,6 +262,7 @@
"notes.button.hide": "Ocultar Notas",
"courseExit.catalogSearchSuggestion": "¿Quieres saber más? {searchOurCatalogLink} para buscar más cursos y programas por explorar.",
"courseCelebration.certificateBody.available": "\n Muestra tu logro en LinkedIn o en tu currículum hoy mismo.\n Puedes descargar tu certificado ahora y acceder a él en cualquier momento desde tu\n {dashboardLink} y {profileLink}.",
"courseCelebration.certificateBody.notAvailable.endDate.v2": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailableDate}.",
"courseCelebration.certificateBody.unverified": "Para generar un certificado, debes completar la verificación de ID.\n {idVerificationSupportLink} ahora.",
"courseCelebration.certificateBody.upgradable": "No es demasiado tarde para mejorar de categoría. Por {price}, obtendrás acceso a todas las asignaciones\n calificadas de este curso. Al terminar, recibirás un certificado verificado que es una\n valiosa credencial para mejorar tus perspectivas de trabajo y avanzar en tu carrera, o puedes usar dicho\n certificado para destacarlo en solicitudes universitarias.",
"courseCelebration.upgradeDiscountCodePrompt": "Utiliza el código {code} en el momento de la compra para obtener un {percent} % de descuento.",
@@ -274,7 +275,7 @@
"courseCelebration.dashboardInfo": "Puedes acceder a este curso y a sus materiales en tu {dashboardLink}.",
"courseExit.programs.applyForCredit": "Solicitar crédito",
"courseCelebration.certificateHeader.downloadable": "¡Tu certificado está disponible!",
"courseCelebration.certificateHeader.notAvailable": "Tu calificación y tu certificado estarán listos en breve.",
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate status will be available soon.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "Si has obtenido una calificación de aprobado, tu certificado se emitirá automáticamente.",
"courseCelebration.certificateHeader.unverified": "Debes completar la verificación para recibir tu certificado.",
"courseCelebration.certificateHeader.requestable": "¡Felicitaciones, usted califica para recibir un certificado!",
@@ -396,7 +397,7 @@
"learning.generic.upsell.fullAccessBullet.fullAccess": "Acceso completo",
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} al contenido y los materiales del curso, incluso después de que finalice el curso",
"learning.generic.upsell.supportMissionBullet.mission": "misión",
"learning.generic.upsell.supportMissionBullet": "Apoya nuestra {missionInBoldText} en edX",
"learning.generic.upsell.supportMissionBullet": "Apoya nuestra {missionInBoldText} en {siteName}",
"masquerade-widget.userName.error.generic": "Se ha producido un error. Inténtalo de nuevo.",
"masquerade-widget.userName.input.placeholder": "Nombre de usuario o correo electrónico",
"masquerade-widget.userName.input.label": "Hazte pasar por este usuario",
@@ -408,7 +409,7 @@
"tours.button.okay": "Okey",
"tours.button.beginTour": "Comenzar recorrido",
"tours.button.launchTour": "gira de lanzamiento",
"tours.newUserModal.body": "Hagamos un recorrido rápido por edX para que pueda aprovechar al máximo su curso.",
"tours.newUserModal.body": "Hagamos un recorrido rápido por {siteName} para que pueda aprovechar al máximo su curso.",
"tours.newUserModal.title.welcome": "Bienvenido a tu",
"tours.button.skipForNow": "Saltar por ahora ",
"tours.datesCheckpoint.body": "Las fechas importantes pueden ayudarlo a mantenerse encaminado.",

View File

@@ -34,8 +34,8 @@
"learning.goals.unsubscribe.header": "Vous vous êtes désabonné des rappels d'objectifs",
"learning.goals.unsubscribe.loading": "Désinscription...",
"learning.goals.unsubscribe.errorDescription": "Nous n'avons pas pu vous désinscrire des courriels de rappel d'objectif. Veuillez réessayer plus tard ou {contactSupport} pour obtenir de l'aide.",
"learning.outline.alert.cert.when": "Ce cours se termine le {courseEndDateFormatted}. Les notes finales et les attestations\n devraient être disponibles après {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header": "Votre note et votre attestation seront bientôt prêtes !",
"learning.outline.alert.cert.earnedNotAvailable": "This course ends on {courseEndDateFormatted}. Final grades and any earned certificates are\n scheduled to be available after {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header.v2": "Your grade and certificate status will be available soon.",
"cert.alert.earned.ready.header": "Félicitations ! Votre attestation est prête.",
"cert.alert.notPassing.header": "Vous n'êtes pas encore éligible pour une attestation",
"cert.alert.notPassing.button": "Voir les notes",
@@ -113,7 +113,7 @@
"learning.outline.sequence-due": "{description} échéance {assignmentDue}",
"progress.certificateStatus.unverifiedBody": "Afin de générer une attestation, vous devez effectuer une vérification d'identité. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Présentez vos réalisations sur LinkedIn ou votre curriculum vitae aujourd'hui. Vous pouvez télécharger votre certificat maintenant et y accéder à tout moment depuis votre tableau de bord et votre profil.",
"courseCelebration.certificateBody.notAvailable.endDate": "Ce cours se termine le {endDate} et les notes finales et les attestations sont programmées pour être\n disponibles après le {certAvailableDate}.",
"courseCelebration.certificateBody.notAvailable.endDate": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailabilityDate}.",
"progress.certificateStatus.notPassingHeader": "État de l'attestation",
"progress.certificateStatus.notPassingBody": "Pour être admissible à une attestation, vous devez avoir la note de passage.",
"progress.certificateStatus.inProgressHeader": "Plus de contenu sera bientôt disponible!",
@@ -131,7 +131,7 @@
"progress.certificateStatus.upgradeHeader": "Obtenir un certificat",
"progress.certificateStatus.upgradeBody": "Vous êtes dans une piste d'audit et n'êtes pas admissible à une attestation. Afin d'obtenir vers une attestation, mettez à niveau votre cours dès aujourd'hui.",
"progress.certificateStatus.upgradeButton": "Mettre à jour dès maintenant",
"progress.certificateStatus.unverifiedHomeHeader": "Vérifiez votre identité pour obtenir une attestation !",
"progress.certificateStatus.unverifiedHomeHeader.v2": "Verify your identity to qualify for a certificate.",
"progress.certificateStatus.unverifiedHomeButton": "Vérifiez mon identité",
"progress.certificateStatus.unverifiedHomeBody": "Afin de générer une attestation pour ce cours, vous devez compléter le processus de vérification d'identité.",
"progress.completion.donut.label": "achevée",
@@ -262,6 +262,7 @@
"notes.button.hide": "Masquer les notes",
"courseExit.catalogSearchSuggestion": "Vous souhaitez en apprendre plus? {searchOurCatalogLink} pour trouver plus de cours et de programmes à explorer.",
"courseCelebration.certificateBody.available": "\n Affichez vos accomplissements sur LinkedIn ou votre CV dès aujourd'hui.\n Vous pouvez télécharger votre attestation maintenant et y accéder à tout moment depuis vos\n {dashboardLink} et {profileLink}.",
"courseCelebration.certificateBody.notAvailable.endDate.v2": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailableDate}.",
"courseCelebration.certificateBody.unverified": "Afin de générer une attestation, vous devez effectuer une vérification d'identité.\n {idVerificationSupportLink} maintenant.",
"courseCelebration.certificateBody.upgradable": "Il nest pas trop tard pour effectuer une mise à niveau. Pour {price}, vous débloquerez l'accès à tous les\n devoirs dans ce cours. À la fin, vous recevrez une attestation qui est une source\n d'informations précieuses pour améliorer vos perspectives d'emploi et faire progresser votre carrière, ou mettre en valeur votre\n attestation dans des demandes d'admission.",
"courseCelebration.upgradeDiscountCodePrompt": "Utilisez le code {code} lors du paiement pour {percent}% de réduction!",
@@ -274,7 +275,7 @@
"courseCelebration.dashboardInfo": "Vous pouvez accéder à ce cours et à ses supports sur votre {dashboardLink}.",
"courseExit.programs.applyForCredit": "Demander un crédit",
"courseCelebration.certificateHeader.downloadable": "Votre attestation est disponible!",
"courseCelebration.certificateHeader.notAvailable": "Votre note et votre attestation seront bientôt prêtes !",
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate status will be available soon.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "Si vous avez obtenu une note de passage, votre attestation sera automatiquement générée.",
"courseCelebration.certificateHeader.unverified": "Vous devez avoir complété votre vérification pour recevoir votre attestation.",
"courseCelebration.certificateHeader.requestable": "Félicitations, vous avez terminé le processus pour passer un certificat !",
@@ -396,7 +397,7 @@
"learning.generic.upsell.fullAccessBullet.fullAccess": "Accès complet",
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} au contenu et aux supports du cours, même après la fin du cours",
"learning.generic.upsell.supportMissionBullet.mission": "mission",
"learning.generic.upsell.supportMissionBullet": "Supportez notre {missionInBoldText} chez edX",
"learning.generic.upsell.supportMissionBullet": "Supportez notre {missionInBoldText} chez {siteName}",
"masquerade-widget.userName.error.generic": "Une erreur est survenue; veuillez réessayer.",
"masquerade-widget.userName.input.placeholder": "Nom d'utilisateur ou courriel",
"masquerade-widget.userName.input.label": "Se faire passer pour cet utilisateur",
@@ -408,7 +409,7 @@
"tours.button.okay": "Okay",
"tours.button.beginTour": "Commencer la visite guidée",
"tours.button.launchTour": "Lancer la visite guidée",
"tours.newUserModal.body": "Faisons une visite guidée de edX afin de profiter au maximum de votre cours.",
"tours.newUserModal.body": "Faisons une visite guidée de {siteName} afin de profiter au maximum de votre cours.",
"tours.newUserModal.title.welcome": "Bienvenue à votre",
"tours.button.skipForNow": "Ignorer pour l'instant",
"tours.datesCheckpoint.body": "Dates importantes afin de vous maintenir sur la bonne voie.",

View File

@@ -34,8 +34,8 @@
"learning.goals.unsubscribe.header": "Youve unsubscribed from goal reminders",
"learning.goals.unsubscribe.loading": "Unsubscribing…",
"learning.goals.unsubscribe.errorDescription": "We were unable to unsubscribe you from goal reminder emails. Please try again later or {contactSupport} for help.",
"learning.outline.alert.cert.when": "This course ends on {courseEndDateFormatted}. Final grades and certificates are\n scheduled to be available after {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header": "Your grade and certificate will be ready soon!",
"learning.outline.alert.cert.earnedNotAvailable": "This course ends on {courseEndDateFormatted}. Final grades and any earned certificates are\n scheduled to be available after {certificateAvailableDate}.",
"cert.alert.earned.unavailable.header.v2": "Your grade and certificate status will be available soon.",
"cert.alert.earned.ready.header": "Congratulations! Your certificate is ready.",
"cert.alert.notPassing.header": "You are not yet eligible for a certificate",
"cert.alert.notPassing.button": "View grades",
@@ -113,7 +113,7 @@
"learning.outline.sequence-due": "{description} due {assignmentDue}",
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resumé today. You can download your certificate now and access it any time from your Dashboard and Profile.",
"courseCelebration.certificateBody.notAvailable.endDate": "This course ended on {endDate} and final grades and certificates are scheduled to be\n available after {certAvailableDate}.",
"courseCelebration.certificateBody.notAvailable.endDate": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailabilityDate}.",
"progress.certificateStatus.notPassingHeader": "Certificate status",
"progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.",
"progress.certificateStatus.inProgressHeader": "More content is coming soon!",
@@ -131,7 +131,7 @@
"progress.certificateStatus.upgradeHeader": "Earn a certificate",
"progress.certificateStatus.upgradeBody": "You are in an audit track and do not qualify for a certificate. In order to work towards a certificate, upgrade your course today.",
"progress.certificateStatus.upgradeButton": "Upgrade now",
"progress.certificateStatus.unverifiedHomeHeader": "Verify your identity to earn a certificate!",
"progress.certificateStatus.unverifiedHomeHeader.v2": "Verify your identity to qualify for a certificate.",
"progress.certificateStatus.unverifiedHomeButton": "Verify my ID",
"progress.certificateStatus.unverifiedHomeBody": "In order to generate a certificate for this course, you must complete the ID verification process.",
"progress.completion.donut.label": "completed",
@@ -262,6 +262,7 @@
"notes.button.hide": "Hide Notes",
"courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.",
"courseCelebration.certificateBody.available": "\n Showcase your accomplishment on LinkedIn or your resumé today.\n You can download your certificate now and access it any time from your\n {dashboardLink} and {profileLink}.",
"courseCelebration.certificateBody.notAvailable.endDate.v2": "This course ends on {endDate}. Final grades and any earned certificates are\n scheduled to be available after {certAvailableDate}.",
"courseCelebration.certificateBody.unverified": "In order to generate a certificate, you must complete ID verification.\n {idVerificationSupportLink} now.",
"courseCelebration.certificateBody.upgradable": "Its not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.",
"courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!",
@@ -274,7 +275,7 @@
"courseCelebration.dashboardInfo": "You can access this course and its materials on your {dashboardLink}.",
"courseExit.programs.applyForCredit": "Apply for credit",
"courseCelebration.certificateHeader.downloadable": "Your certificate is available!",
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate will be ready soon!",
"courseCelebration.certificateHeader.notAvailable": "Your grade and certificate status will be available soon.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
"courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.",
"courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!",
@@ -396,7 +397,7 @@
"learning.generic.upsell.fullAccessBullet.fullAccess": "Full access",
"learning.generic.upsell.fullAccessBullet": "{fullAccessInBoldText} to course content and materials, even after the course ends",
"learning.generic.upsell.supportMissionBullet.mission": "mission",
"learning.generic.upsell.supportMissionBullet": "Support our {missionInBoldText} at edX",
"learning.generic.upsell.supportMissionBullet": "Support our {missionInBoldText} at {siteName}",
"masquerade-widget.userName.error.generic": "An error has occurred; please try again.",
"masquerade-widget.userName.input.placeholder": "Username or email",
"masquerade-widget.userName.input.label": "Masquerade as this user",
@@ -408,7 +409,7 @@
"tours.button.okay": "Okay",
"tours.button.beginTour": "Begin tour",
"tours.button.launchTour": "Launch tour",
"tours.newUserModal.body": "Lets take a quick tour of edX so you can get the most out of your course.",
"tours.newUserModal.body": "Lets take a quick tour of {siteName} so you can get the most out of your course.",
"tours.newUserModal.title.welcome": "Welcome to your",
"tours.button.skipForNow": "Skip for now",
"tours.datesCheckpoint.body": "Important dates can help you stay on track.",

View File

@@ -13,7 +13,7 @@ const messages = defineMessages({
},
newUserModalBody: {
id: 'tours.newUserModal.body',
defaultMessage: 'Lets take a quick tour of edX so you can get the most out of your course.',
defaultMessage: 'Lets take a quick tour of {siteName} so you can get the most out of your course.',
},
newUserModalTitleWelcome: {
id: 'tours.newUserModal.title.welcome',

View File

@@ -58,7 +58,7 @@ function NewUserCourseHomeTourModal({
)}
onClose={onDismiss}
>
<p className="text-dark-900">{intl.formatMessage(messages.newUserModalBody)}</p>
<p className="text-dark-900">{intl.formatMessage(messages.newUserModalBody, { siteName: getConfig().SITE_NAME })}</p>
</MarketingModal>
);
}

View File

@@ -5,14 +5,13 @@ import '@testing-library/jest-dom/extend-expect';
import './courseware/data/__factories__';
import './course-home/data/__factories__';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import { configure as configureI18n } from '@edx/frontend-platform/i18n';
import { configure as configureI18n, IntlProvider } from '@edx/frontend-platform/i18n';
import { configure as configureLogging } from '@edx/frontend-platform/logging';
import { configure as configureAuth, getAuthenticatedHttpClient, MockAuthService } from '@edx/frontend-platform/auth';
import React from 'react';
import PropTypes from 'prop-types';
import { render as rtlRender } from '@testing-library/react';
import { configureStore } from '@reduxjs/toolkit';
import { IntlProvider } from 'react-intl';
import MockAdapter from 'axios-mock-adapter';
import AppProvider from '@edx/frontend-platform/react/AppProvider';
import { reducer as courseHomeReducer } from './course-home/data';

View File

@@ -160,8 +160,8 @@ function StreakModal({
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body className="modal-body">
<p>{intl.formatMessage(messages.streakBody)}</p>
<p className="modal-image">
<p className="text-center">{intl.formatMessage(messages.streakBody)}</p>
<p className="modal-image text-center">
{!wideScreen && <img src={StreakMobileImage} alt="" className="img-fluid" />}
{wideScreen && <img src={StreakDesktopImage} alt="" className="img-fluid" />}
</p>

View File

@@ -26,6 +26,10 @@
.modal-body {
padding-top: .5rem;
font-size: 1.2rem;
.pgn__modal-body-content {
text-align: center;
}
}
.modal-footer {