/
dashboard.html
403 lines (372 loc) · 20.7 KB
/
dashboard.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
<%page expression_filter="h"/>
<%inherit file="main.html" />
<%def name="online_help_token()"><% return "learnerdashboard" %></%def>
<%namespace name='static' file='static_content.html'/>
<%!
import pytz
from datetime import datetime, timedelta
from django.urls import reverse
from django.utils.translation import gettext as _
from django.template import RequestContext
from common.djangoapps.entitlements.models import CourseEntitlement
from common.djangoapps.third_party_auth import pipeline
from common.djangoapps.util.date_utils import strftime_localized
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
from openedx.core.djangolib.markup import HTML, Text
from common.djangoapps.student.models import CourseEnrollment
%>
<%
cert_name_short = settings.CERT_NAME_SHORT
cert_name_long = settings.CERT_NAME_LONG
%>
<%block name="pagetitle">${_("Dashboard")}</%block>
<%block name="bodyclass">view-dashboard is-authenticated</%block>
<%block name="header_extras">
% for template_name in ["donation"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="dashboard/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="js_extra">
<script src="${static.url('js/commerce/credit.js')}"></script>
<script type="text/javascript" src="${static.url('js/learner_dashboard/certificate_api.js')}"></script>
<%static:js group='dashboard'/>
<script type="text/javascript">
$(document).ready(function() {
edx.dashboard.legacy.init({
dashboard: "${reverse('dashboard') | n, js_escaped_string}",
signInUser: "${reverse('signin_user') | n, js_escaped_string}",
changeEmailSettings: "${reverse('change_email_settings') | n, js_escaped_string}",
sendAccountActivationEmail: "${reverse('send_account_activation_email') | n, js_escaped_string}"
});
});
</script>
<%static:webpack entry="UnenrollmentFactory">
UnenrollmentFactory({
urls: {
dashboard: "${reverse('dashboard') | n, js_escaped_string}",
signInUser: "${reverse('signin_user') | n, js_escaped_string}",
changeEmailSettings: "${reverse('change_email_settings') | n, js_escaped_string}",
browseCourses: "${marketing_link('COURSES') | n, js_escaped_string}"
},
isEdx: false
});
</%static:webpack>
<%static:webpack entry="EntitlementUnenrollmentFactory">
## Wait until the document is fully loaded before initializing the EntitlementUnenrollmentView
## to ensure events are setup correctly.
$(document).ready(function() {
EntitlementUnenrollmentFactory({
dashboardPath: "${reverse('dashboard') | n, js_escaped_string}",
signInPath: "${reverse('signin_user') | n, js_escaped_string}",
browseCourses: "${marketing_link('COURSES') | n, js_escaped_string}",
isEdx: false
});
});
</%static:webpack>
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory">
DashboardSearchFactory();
</%static:require_module>
% endif
% if redirect_message:
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
var banner = new MessageBannerView({urgency: 'low', type: 'warning'});
$('#content').prepend(banner.$el);
banner.showMessage(${redirect_message | n, dump_js_escaped_json})
</%static:require_module>
% endif
% if recovery_email_message:
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
var banner = new MessageBannerView({urgency: 'low', type: 'warning', hideCloseBtn: false, isRecoveryEmailMsg: true});
$('#content').prepend(banner.$el);
banner.showMessage(${recovery_email_message | n, dump_js_escaped_json})
</%static:require_module>
% endif
% if recovery_email_activation_message:
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
var banner = new MessageBannerView({urgency: 'low', type: 'warning', isRecoveryEmailMsg: true});
$('#content').prepend(banner.$el);
banner.showMessage(${recovery_email_activation_message | n, dump_js_escaped_json})
</%static:require_module>
% endif
% if enterprise_learner_portal_enabled_message:
<%static:require_module module_name="js/views/message_banner" class_name="MessageBannerView">
var banner = new MessageBannerView({urgency: 'low', type: 'warning', isLearnerPortalEnabled: true});
$('#content').prepend(banner.$el);
banner.showMessage(${enterprise_learner_portal_enabled_message | n, dump_js_escaped_json})
</%static:require_module>
% endif
</%block>
<div class="dashboard-notifications" tabindex="-1">
%if banner_account_activation_message:
<div class="dashboard-banner">
${banner_account_activation_message | n, decode.utf8}
</div>
%endif
%if enrollment_message:
<div class="dashboard-banner">
${enrollment_message | n, decode.utf8}
</div>
%endif
%if enterprise_message:
<div class="dashboard-banner">
${ enterprise_message | n, decode.utf8 }
</div>
%endif
%if account_activation_messages:
<div class="activation-message-container">
% for account_activation_message in account_activation_messages:
<div class="account-activation ${account_activation_message.tags}" role="alert" aria-label="Account Activation Message" tabindex="-1">
<div class="message-copy" >
${ account_activation_message | n, decode.utf8 }
</div>
</div>
% endfor
</div>
%endif
</div>
<main id="main" aria-label="Content" tabindex="-1">
<div class="dashboard" id="dashboard-main">
<div class="main-container">
<div class="my-courses" id="my-courses">
% if display_dashboard_courses:
<%include file="learner_dashboard/_dashboard_navigation_courses.html"/>
% endif
% if len(course_entitlements + course_enrollments) > 0:
<ul class="listing-courses">
<%
share_settings = configuration_helpers.get_value(
'SOCIAL_SHARING_SETTINGS',
getattr(settings, 'SOCIAL_SHARING_SETTINGS', {})
)
%>
% for dashboard_index, enrollment in enumerate(course_entitlements + course_enrollments):
<%
# Check if the course run is an entitlement and if it has an associated session
entitlement = enrollment if isinstance(enrollment, CourseEntitlement) else None
entitlement_session = entitlement.enrollment_course_run if entitlement else None
entitlement_days_until_expiration = entitlement.get_days_until_expiration() if entitlement else None
entitlement_expiration = datetime.now(tz=pytz.UTC) + timedelta(days=entitlement_days_until_expiration) if (entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None
entitlement_expiration_date = strftime_localized(entitlement_expiration, 'SHORT_DATE') if entitlement and entitlement_expiration else None
entitlement_expired_at = strftime_localized(entitlement.expired_at_datetime, 'SHORT_DATE') if entitlement and entitlement.expired_at_datetime else None
is_fulfilled_entitlement = True if entitlement and entitlement_session else False
is_unfulfilled_entitlement = True if entitlement and not entitlement_session else False
entitlement_available_sessions = []
if entitlement:
# Grab the available, enrollable sessions for a given entitlement and scrape them for relevant attributes
entitlement_available_sessions = [{
'session_id': course['key'],
'enrollment_end': course['enrollment_end'],
'pacing_type': course['pacing_type'],
'advertised_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
'start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
'end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
} for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
if is_fulfilled_entitlement:
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session
else:
# If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object to populate card with metadata
pseudo_session = unfulfilled_entitlement_pseudo_sessions[str(entitlement.uuid)]
if not pseudo_session:
continue
pseudo_key = pseudo_session['key']
if not isinstance(pseudo_key, CourseKey):
pseudo_key = CourseKey.from_string(pseudo_session['key'])
enrollment = CourseEnrollment(user=user, course=CourseOverview.get_from_id(pseudo_key), mode=pseudo_session['type'])
# We only show email settings for entitlement cards if the entitlement has an associated enrollment
show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for)
course_overview = enrollment.course_overview
else:
show_email_settings = (enrollment.course_id in show_email_settings_for)
course_overview = CourseOverview.get_from_id(enrollment.course_id)
session_id = enrollment.course_id
show_courseware_link = show_courseware_links_for.get(session_id, False)
cert_status = cert_statuses.get(session_id)
can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable()
partner_managed_enrollment = enrollment.mode == 'masters'
# checks if we can unenroll based on the value of partner_managed_enrollment
can_unenroll_partner_managed_enrollment = False if partner_managed_enrollment else (not cert_status)
# checks if we can unenroll based on the value of unfulfilled_entitlement
can_unenroll_unfulfilled_entitlement = cert_status.get('can_unenroll') if cert_status and not unfulfilled_entitlement else False
# compares the three different parameters by which we can unenroll
can_unenroll = (can_unenroll_partner_managed_enrollment or can_unenroll_unfulfilled_entitlement) and not disable_unenrollment
credit_status = credit_statuses.get(session_id)
course_mode_info = all_course_modes.get(session_id)
is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid)
is_course_voucher_refundable = (session_id in enrolled_courses_voucher_refundable)
course_requirements = courses_requirements_not_met.get(session_id)
related_programs = inverted_programs.get(str(entitlement.course_uuid if is_unfulfilled_entitlement else session_id))
show_consent_link = (session_id in consent_required_courses)
resume_button_url = resume_button_urls[dashboard_index]
%>
<%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, enrollments_fbe_is_on=enrollments_fbe_is_on, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_voucher_refundable=is_course_voucher_refundable, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name, resume_button_url=resume_button_url, partner_managed_enrollment=partner_managed_enrollment' />
% endfor
% if show_load_all_courses_link:
<br/>
${len(course_enrollments)} ${_("results successfully populated,")}
<a href="${reverse('dashboard')}?course_limit=None">
${_("Click to load all enrolled courses")}
</a>
% endif
</ul>
% else:
<div class="empty-dashboard-message">
% if display_dashboard_courses:
<p>${_("You are not enrolled in any courses yet.")}</p>
% if empty_dashboard_message:
<p class="custom-message">${empty_dashboard_message | n, decode.utf8}</p>
%endif
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
<a class="btn btn-primary" href="${marketing_link('COURSES')}">
${_("Explore courses")}
</a>
%endif
% else:
<p>${_("Activate your account!")}</p>
<p class="custom-message">${ activate_account_message | n, decode.utf8 }</p>
% endif
</div>
% endif
% if staff_access and len(errored_courses) > 0:
<div id="course-errors">
<h2>${_("Course-loading errors")}</h2>
% for course_dir, errors in errored_courses.items():
<h3>${course_dir}</h3>
<ul>
% for (msg, err) in errors:
<li>${msg}
<ul><li><pre>${err}</pre></li></ul>
</li>
% endfor
</ul>
% endfor
</div>
% endif
</div>
</div>
<div class="side-container" role="complementary" aria-label="messages">
%if display_sidebar_account_activation_message:
<div class="sidebar-notification">
<%include file="${static.get_template_path('registration/account_activation_sidebar_notice.html')}" />
</div>
%endif
% if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'):
<div id="dashboard-search-bar" class="search-bar dashboard-search-bar" role="search" aria-label="Dashboard">
<form class="search-form">
<label for="dashboard-search-input">${_('Search Your Courses')}</label>
<div class="search-field-wrapper">
<input id="dashboard-search-input" type="text" class="search-field"/>
<button type="submit" class="search-button" title="${_('Search')}">
<span class="icon fa fa-search" aria-hidden="true"></span>
</button>
<button type="button" class="cancel-button" title="${_('Clear search')}">
<span class="icon fa fa-remove" aria-hidden="true"></span>
</button>
</div>
</form>
</div>
<div id="dashboard-search-results" class="search-results dashboard-search-results"></div>
% endif
<%block name="skip_links">
% if settings.FEATURES.get('ENABLE_ANNOUNCEMENTS'):
<a id="announcements-skip" class="nav-skip sr-only sr-only-focusable" href="#announcements">${_("Skip to list of announcements")}</a>
% endif
</%block>
% if settings.FEATURES.get('ENABLE_ANNOUNCEMENTS'):
<%include file='dashboard/_dashboard_announcements.html' />
% endif
</div>
</div>
</main>
%if show_account_activation_popup:
<div id="activate-account-modal" class="modal activate-account-modal" aria-hidden="true" tabindex=0 >
<div class="inner-wrapper" role="dialog" aria-labelledby="activate-account-modal-title" aria-live="polite">
<h3>
${_("Activate your account so you can log back in")}
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("window open")}
</span>
</h3>
<p class="activate-account-modal-body">${Text(_("We sent an email to {strong_start}{email}{strong_end} with a link to activate your account. Can’t find it? Check your spam folder or {link_start}resend the email{link_end}.")).format(
strong_start=HTML('<strong>'),
email=user.email,
strong_end=HTML('</strong>'),
link_start=HTML('<a href="#" id="send_cta_email" >'),
link_end=HTML('</a>')
)}
</p>
<div class="activate-account-modal-button">
<button class="btn btn-primary" id="button">
${Text(_("Continue to {platform_name}")).format(platform_name=settings.PLATFORM_NAME)}
<svg style="vertical-align:bottom" width="24" height="24" viewBox="0 0 24 24" fill="white" xmlns="http://www.w3.org/2000/svg"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z"/></svg>
</button>
</div>
</div>
</div>
%endif
<div id="email-settings-modal" class="modal" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="email-settings-title">
<button class="close-modal">
<span class="icon fa fa-remove" aria-hidden="true"></span>
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_("Close")}
</span>
</button>
<header>
<h2 id="email-settings-title">
${Text(_("Email Settings for {course_number}")).format(course_number=HTML('<span id="email_settings_course_number"></span>'))}
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("window open")}
</span>
</h2>
<hr/>
</header>
<form id="email_settings_form" method="post">
<input name="course_id" id="email_settings_course_id" type="hidden" />
<label><input type="checkbox" id="receive_emails" name="receive_emails" />${_("Receive course emails")} </label>
<div class="submit">
<input type="submit" id="submit" value="${_("Save Settings")}" />
</div>
</form>
</div>
</div>
<div id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
<div class="inner-wrapper" role="dialog" aria-labelledby="unenrollment-modal-title" aria-live="polite">
<button class="close-modal">
<span class="icon fa fa-remove" aria-hidden="true"></span>
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_("Close")}
</span>
</button>
<header class="unenroll-header">
<h2 id="unenrollment-modal-title">
<span id='track-info'></span>
<span id='refund-info'></span>
<span class="sr">,
## Translators: this text gives status on if the modal interface (a menu or piece of UI that takes the full focus of the screen) is open or not
${_("window open")}
</span>
</h2>
<hr/>
</header>
<div id="unenroll_error" class="modal-form-error"></div>
<form id="unenroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
<input name="course_id" id="unenroll_course_id" type="hidden" />
<input name="enrollment_action" type="hidden" value="unenroll" />
<div class="submit">
<input class="submit-button" name="submit" type="submit" value="${_("Unenroll")}" />
</div>
</form>
</div>
</div>
<%include file="dashboard/_dashboard_entitlement_unenrollment_modal.html"/>