update css and html/ add basics for react
LEARNER-2804
This commit is contained in:
10
lms/djangoapps/support/static/support/jsx/.eslintrc.js
Normal file
10
lms/djangoapps/support/static/support/jsx/.eslintrc.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
extends: 'eslint-config-edx',
|
||||
root: true,
|
||||
settings: {
|
||||
'import/resolver': 'webpack',
|
||||
},
|
||||
rules: {
|
||||
'import/prefer-default-export': 'off',
|
||||
},
|
||||
};
|
||||
28
lms/djangoapps/support/static/support/jsx/errors_list.jsx
Normal file
28
lms/djangoapps/support/static/support/jsx/errors_list.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint react/no-array-index-key: 0 */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ShowErrors extends React.Component {
|
||||
|
||||
render() {
|
||||
window.scrollTo(0, 0);
|
||||
return this.props.errorList.length > 0 &&
|
||||
<div className="col-sm-12">
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<strong>{gettext('Please fix the following errors:')}</strong>
|
||||
<ul>
|
||||
{this.props.errorList.map(error =>
|
||||
<li>{error}</li>,
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ShowErrors.propTypes = {
|
||||
errorList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default ShowErrors;
|
||||
170
lms/djangoapps/support/static/support/jsx/file_upload.jsx
Normal file
170
lms/djangoapps/support/static/support/jsx/file_upload.jsx
Normal file
@@ -0,0 +1,170 @@
|
||||
/* global gettext */
|
||||
/* eslint one-var: ["error", "always"] */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ShowProgress from './upload_progress';
|
||||
|
||||
|
||||
class FileUpload extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.uploadFile = this.uploadFile.bind(this);
|
||||
this.removeFile = this.removeFile.bind(this);
|
||||
this.state = {
|
||||
fileList: [],
|
||||
fileInProgress: null,
|
||||
};
|
||||
}
|
||||
|
||||
removeFile(e) {
|
||||
e.preventDefault();
|
||||
const fileToken = e.target.id,
|
||||
$this = this,
|
||||
url = `https://arbisoft.zendesk.com/api/v2/uploads/${fileToken}.json`,
|
||||
accessToken = 'd6ed06821334b6584dd9607d04007c281007324ed07e087879c9c44835c684da',
|
||||
request = new XMLHttpRequest();
|
||||
|
||||
request.open('DELETE', url, true);
|
||||
request.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
|
||||
request.send();
|
||||
|
||||
request.onreadystatechange = function removeFile() {
|
||||
if (request.readyState === 4 && request.status === 204) {
|
||||
$this.setState({
|
||||
fileList: $this.state.fileList.filter(file => file.fileToken !== fileToken),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
uploadFile(e) {
|
||||
const url = 'https://arbisoft.zendesk.com/api/v2/uploads.json?filename=',
|
||||
fileReader = new FileReader(),
|
||||
request = new XMLHttpRequest(),
|
||||
errorList = [],
|
||||
$this = this,
|
||||
file = e.target.files[0],
|
||||
accessToken = 'd6ed06821334b6584dd9607d04007c281007324ed07e087879c9c44835c684da',
|
||||
maxFileSize = 5000000, // 5mb is max limit
|
||||
allowedFileTypes = ['gif', 'png', 'jpg', 'jpeg', 'pdf'];
|
||||
|
||||
// remove file from input and upload it to zendesk after validation
|
||||
$(e.target).val('');
|
||||
|
||||
if (file.size > maxFileSize) {
|
||||
errorList.push(gettext('Files that you upload must be smaller than 5MB in size.'));
|
||||
} else if ($.inArray(file.name.split('.').pop().toLowerCase(), allowedFileTypes) === -1) {
|
||||
errorList.push(gettext('Files that you upload must be PDFs or image files in .gif, .jpg, .jpeg, or .png format.'));
|
||||
}
|
||||
|
||||
this.props.setErrorState(errorList);
|
||||
if (errorList.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
request.open('POST', (url + file.name), true);
|
||||
request.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
request.setRequestHeader('Content-Type', 'application/binary');
|
||||
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
|
||||
fileReader.onloadend = function success() {
|
||||
$this.setState({
|
||||
fileInProgress: file.name,
|
||||
currentRequest: request,
|
||||
});
|
||||
request.send(fileReader.result);
|
||||
};
|
||||
|
||||
request.upload.onprogress = function renderProgress(event) {
|
||||
if (event.lengthComputable) {
|
||||
const percentComplete = (event.loaded / event.total) * 100;
|
||||
$('.progress-bar-striped').css({ width: `${percentComplete}%` });
|
||||
}
|
||||
};
|
||||
|
||||
request.onreadystatechange = function success() {
|
||||
if (request.readyState === 4 && request.status === 201) {
|
||||
const uploadedFile = {
|
||||
fileName: file.name,
|
||||
fileToken: JSON.parse(request.response).upload.token,
|
||||
};
|
||||
|
||||
$this.setState(
|
||||
{
|
||||
fileList: $this.state.fileList.concat(uploadedFile),
|
||||
fileInProgress: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function error() {
|
||||
$this.setState({
|
||||
fileInProgress: null,
|
||||
errorList: [gettext('Something went wrong. Please try again later.')],
|
||||
});
|
||||
};
|
||||
|
||||
request.onabort = function abortUpload() {
|
||||
$this.setState({
|
||||
fileInProgress: null,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="file-container">
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
<label htmlFor="attachment">{gettext('Add Attachment')}
|
||||
<span> {gettext('(Optional)')}</span>
|
||||
</label>
|
||||
<input
|
||||
id="attachment"
|
||||
className="file file-loading"
|
||||
type="file"
|
||||
accept=".pdf, .jpeg, .png, .jpg, .gif"
|
||||
onChange={this.uploadFile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="progress-container">
|
||||
{this.state.fileInProgress &&
|
||||
<ShowProgress
|
||||
fileName={this.state.fileInProgress}
|
||||
request={this.state.currentRequest}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className="uploaded-files">
|
||||
{
|
||||
this.state.fileList.map(file =>
|
||||
(<div key={file.fileToken} className="row">
|
||||
<div className="col-sm-12">
|
||||
<span className="file-name">{file.fileName}</span>
|
||||
<span className="file-action remove-upload">
|
||||
<button className="btn btn-link" id={file.fileToken} onClick={this.removeFile}>{gettext('Remove file')}</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>),
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FileUpload.propTypes = {
|
||||
setErrorState: PropTypes.func.isRequired,
|
||||
};
|
||||
export default FileUpload;
|
||||
49
lms/djangoapps/support/static/support/jsx/logged_in_user.jsx
Normal file
49
lms/djangoapps/support/static/support/jsx/logged_in_user.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/* global gettext */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function LoggedInUser({ userInformation }) {
|
||||
return (<div>
|
||||
<div className="row">
|
||||
<div
|
||||
className="col-sm-12 user-info"
|
||||
data-username={userInformation.username}
|
||||
data-email={userInformation.email}
|
||||
>
|
||||
<p>{gettext(`What can we help you with, ${userInformation.username}?`)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
{userInformation.enrollments.length === 0 &&
|
||||
<div>
|
||||
<label htmlFor="course">{gettext('Course Name')}<span> {gettext('(Optional)')}</span></label>
|
||||
<input type="text" className="form-control" id="course" />
|
||||
</div>
|
||||
}
|
||||
{userInformation.enrollments.length > 0 &&
|
||||
<div>
|
||||
<label className="label-course" htmlFor="course">{gettext('Course Name')}</label>
|
||||
<select className="form-control select-course" id="course">
|
||||
{userInformation.enrollments.map(enrollment =>
|
||||
(<option key={enrollment.course_id} value={enrollment.course_id}>
|
||||
{enrollment.course_name}
|
||||
</option>),
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
LoggedInUser.propTypes = {
|
||||
userInformation: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export default LoggedInUser;
|
||||
@@ -0,0 +1,48 @@
|
||||
/* global gettext */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function LoggedOutUser({ loginUrl }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<p>{gettext('Sign in to edX so we can help you better.')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<a href={loginUrl} className="btn btn-primary btn-signin">{gettext('Sign in')}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
<label htmlFor="email">{gettext('Your Email Address')}</label>
|
||||
<input type="text" className="form-control" id="email" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
<label
|
||||
htmlFor="course"
|
||||
>{gettext('Course Name')}<span> {gettext('(Optional)')}</span></label>
|
||||
<input type="text" className="form-control" id="course" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
LoggedOutUser.propTypes = {
|
||||
loginUrl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default LoggedOutUser;
|
||||
@@ -0,0 +1,211 @@
|
||||
/* global gettext */
|
||||
/* eslint one-var: ["error", "always"] */
|
||||
/* eslint no-alert: "error" */
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import FileUpload from './file_upload';
|
||||
import ShowErrors from './errors_list';
|
||||
import LoggedInUser from './logged_in_user';
|
||||
import LoggedOutUser from './logged_out_user';
|
||||
|
||||
// TODO
|
||||
// edx zendesk APIs
|
||||
// access token
|
||||
// custom fields ids
|
||||
// https://openedx.atlassian.net/browse/LEARNER-2736
|
||||
// https://openedx.atlassian.net/browse/LEARNER-2735
|
||||
|
||||
class RenderForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
currentRequest: null,
|
||||
errorList: [],
|
||||
};
|
||||
this.submitForm = this.submitForm.bind(this);
|
||||
this.setErrorState = this.setErrorState.bind(this);
|
||||
}
|
||||
|
||||
setErrorState(errors) {
|
||||
this.setState({
|
||||
errorList: errors,
|
||||
});
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
const url = 'https://arbisoft.zendesk.com/api/v2/tickets.json',
|
||||
$userInfo = $('.user-info'),
|
||||
request = new XMLHttpRequest(),
|
||||
$course = $('#course'),
|
||||
accessToken = 'd6ed06821334b6584dd9607d04007c281007324ed07e087879c9c44835c684da',
|
||||
data = {
|
||||
subject: $('#subject').val(),
|
||||
comment: {
|
||||
body: $('#message').val(),
|
||||
uploads: $.map($('.uploaded-files button'), n => n.id),
|
||||
},
|
||||
};
|
||||
|
||||
let course;
|
||||
|
||||
if ($userInfo.length) {
|
||||
data.requester = $userInfo.data('email');
|
||||
course = $course.find(':selected').text();
|
||||
if (!course.length) {
|
||||
course = $course.val();
|
||||
}
|
||||
} else {
|
||||
data.requester = $('#email').val();
|
||||
course = $course.val();
|
||||
}
|
||||
|
||||
data.custom_fields = [{
|
||||
id: '114099484092',
|
||||
value: course,
|
||||
}];
|
||||
|
||||
if (this.validateData(data)) {
|
||||
request.open('POST', url, true);
|
||||
request.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
request.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
|
||||
request.send(JSON.stringify({
|
||||
ticket: data,
|
||||
}));
|
||||
|
||||
request.onreadystatechange = function success() {
|
||||
if (request.readyState === 4 && request.status === 201) {
|
||||
// TODO needs to remove after implementing success page
|
||||
const alert = 'Request submitted successfully.';
|
||||
alert();
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function error() {
|
||||
this.setErrorState([gettext('Something went wrong. Please try again later.')]);
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
validateData(data) {
|
||||
const errors = [],
|
||||
regex = /^([a-zA-Z0-9_.+-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
|
||||
|
||||
if (!data.requester) {
|
||||
errors.push(gettext('Enter a valid email address.'));
|
||||
$('#email').closest('.form-group').addClass('has-error');
|
||||
} else if (!regex.test(data.requester)) {
|
||||
errors.push(gettext('Enter a valid email address.'));
|
||||
$('#email').closest('.form-group').addClass('has-error');
|
||||
}
|
||||
if (!data.subject) {
|
||||
errors.push(gettext('Enter a subject for your support request.'));
|
||||
$('#subject').closest('.form-group').addClass('has-error');
|
||||
}
|
||||
if (!data.comment.body) {
|
||||
errors.push(gettext('Enter some details for your support request.'));
|
||||
$('#message').closest('.form-group').addClass('has-error');
|
||||
}
|
||||
|
||||
if (!errors.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.setErrorState(errors);
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
let userElement;
|
||||
if (this.props.context.user) {
|
||||
userElement = <LoggedInUser userInformation={this.props.context.user} />;
|
||||
} else {
|
||||
userElement = <LoggedOutUser loginUrl={this.props.context.loginQuery} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="contact-us-wrapper">
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<h2>{gettext('Contact Us')}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row form-errors">
|
||||
<ShowErrors errorList={this.state.errorList} />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<p>{gettext('Your question might have already been answered.')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<a
|
||||
href={this.props.context.marketingUrl}
|
||||
className="btn btn-secondary help-button"
|
||||
>{gettext('Search the edX Help Center')}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{userElement}
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
<label htmlFor="subject">{gettext('Subject')}</label>
|
||||
<input type="text" className="form-control" id="subject" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
<label htmlFor="message">{gettext('Details')}</label>
|
||||
<p
|
||||
className="message-desc"
|
||||
>{gettext('The more you tell us, the more quickly and helpfully we can respond!')}</p>
|
||||
<textarea
|
||||
aria-describedby="message"
|
||||
className="form-control"
|
||||
rows="7"
|
||||
id="message"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FileUpload setErrorState={this.setErrorState} />
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<button
|
||||
className="btn btn-primary btn-submit"
|
||||
onClick={this.submitForm}
|
||||
>{gettext('Submit')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RenderForm.propTypes = {
|
||||
context: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
};
|
||||
|
||||
export class SingleSupportForm {
|
||||
constructor(context) {
|
||||
ReactDOM.render(
|
||||
<RenderForm context={context} />,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* global gettext */
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class ShowProgress extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.abortRequest = this.abortRequest.bind(this);
|
||||
}
|
||||
|
||||
abortRequest(e) {
|
||||
e.preventDefault();
|
||||
this.props.request.abort();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<div className="form-group">
|
||||
<span className="file-name">{this.props.fileName}</span>
|
||||
<span className="file-action abort-upload">
|
||||
<button className="btn btn-link" onClick={this.abortRequest}>{gettext('Cancel upload')}</button>
|
||||
</span>
|
||||
<div className="progress">
|
||||
<div className="progress-bar progress-bar-striped zero-width" role="progressbar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShowProgress.propTypes = {
|
||||
fileName: PropTypes.string.isRequired,
|
||||
request: PropTypes.objectOf(XMLHttpRequest).isRequired,
|
||||
};
|
||||
|
||||
export default ShowProgress;
|
||||
@@ -380,46 +380,3 @@ class SupportViewEnrollmentsTests(SharedModuleStoreTestCase, SupportViewTestCase
|
||||
)
|
||||
verified_mode.expiration_datetime = datetime(year=1970, month=1, day=9, tzinfo=UTC)
|
||||
verified_mode.save()
|
||||
|
||||
|
||||
class ContactUsViewTests(ModuleStoreTestCase):
|
||||
|
||||
url = reverse('support:contact_us')
|
||||
|
||||
def setUp(self):
|
||||
super(ContactUsViewTests, self).setUp()
|
||||
self.user = UserFactory()
|
||||
self.client.login(username=self.user.username, password='test')
|
||||
self.user_enrollment = CourseEnrollmentFactory.create(
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
def test_get_with_logged_in_user(self):
|
||||
""" Verify that logged in users will see courses dropdown."""
|
||||
response = self.client.get(self.url)
|
||||
expected = '<option value="{course_id}">'.format(course_id=self.user_enrollment.course.id)
|
||||
self.assertContains(response, expected)
|
||||
|
||||
def test_get_without_course_enrollment(self):
|
||||
""" Verify that logged in users will see not courses dropdown,
|
||||
if they are not enrolled in any course.
|
||||
"""
|
||||
self.client.logout()
|
||||
new_user = UserFactory()
|
||||
self.client.login(username=new_user.username, password='test')
|
||||
response = self.client.get(self.url)
|
||||
self._assert_without_course_enrollment(response)
|
||||
|
||||
def test_get_with_anonymous_user(self):
|
||||
""" Verify that logged out users will see not courses dropdown.
|
||||
They will see sign in button.
|
||||
"""
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, 'class="btn btn-primary btn-signin">Sign in</a>')
|
||||
self._assert_without_course_enrollment(response)
|
||||
|
||||
def _assert_without_course_enrollment(self, response):
|
||||
""" Assert that users will not see simple course text input."""
|
||||
expected = '<input type="text" class="form-control" id="course">'
|
||||
self.assertContains(response, expected)
|
||||
|
||||
@@ -165,7 +165,6 @@
|
||||
|
||||
.help-button {
|
||||
margin-bottom: $baseline;
|
||||
width: $baseline * 8;
|
||||
height: $baseline * 2;
|
||||
font-weight: $font-regular;
|
||||
font-size: $support-form-base-font-size + 2;
|
||||
@@ -203,7 +202,10 @@
|
||||
|
||||
.progress-bar {
|
||||
background-color: $blue;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.zero-width {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +233,10 @@
|
||||
|
||||
font-size: $support-form-base-font-size + 2;
|
||||
margin-bottom: $baseline - 10;
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-signin {
|
||||
@@ -245,6 +251,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
font-size: $support-form-base-font-size - 2;
|
||||
font-family: $sans-serif;
|
||||
font-style: normal;
|
||||
font-weight: $font-regular;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: $palette-error-text !important;
|
||||
font-size: $support-form-base-font-size;
|
||||
}
|
||||
|
||||
.has-error{
|
||||
label{
|
||||
color: $danger-red;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 768px) {
|
||||
.row {
|
||||
max-width: $baseline * 25;
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
%>
|
||||
|
||||
<%inherit file="../main.html"/>
|
||||
<%namespace file='../main.html' import="login_query"/>
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<%block name="title">
|
||||
<title>
|
||||
@@ -19,142 +21,35 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
<%block name="body">
|
||||
|
||||
<div class="container contact-us-wrapper">
|
||||
<div id="root" class="container">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h2>${_("Contact Us")}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<%static:webpack entry="SingleSupportForm">
|
||||
var context = {
|
||||
'marketingUrl': "${marketing_link('FAQ') | n, js_escaped_string}",
|
||||
'loginQuery': "/login${login_query() | n, js_escaped_string}",
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>${_("Your question may have already been answered.")}</p>
|
||||
</div>
|
||||
</div>
|
||||
% if user.is_authenticated():
|
||||
context['user'] = {
|
||||
'username': "${user.username | n, js_escaped_string}",
|
||||
'email': "${user.email | n, js_escaped_string}"
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<a href="${marketing_link('FAQ')}" class="btn btn-secondary help-button">${_("Visit edX Help")}</a>
|
||||
</div>
|
||||
</div>
|
||||
<!--logged out users-->
|
||||
% if user_enrollments:
|
||||
enrollments = []
|
||||
% for enrollment in user_enrollments:
|
||||
enrollments.push({
|
||||
'course_id': "${enrollment.course.id | n, js_escaped_string}",
|
||||
'course_name': "${enrollment.course.display_name | n, js_escaped_string}",
|
||||
})
|
||||
%endfor
|
||||
context['user']['enrollments'] = enrollments
|
||||
|
||||
% if not user.is_authenticated():
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>${_("Sign in for a faster response")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sign-in button brings user to sign-in page. After signing in, user is brough to logged in state of contact form.-->
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<a href="/login${login_query()}" class="btn btn-primary btn-signin">${_("Sign in")}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- No autofilled email in logged out state.-->
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="email">${_("Email")}</label>
|
||||
<input type="text" class="form-control" id="email">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="course">${_("Course Name")}<span> ${_("(Optional)")}</span></label>
|
||||
<input type="text" class="form-control" id="course">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
% else:
|
||||
|
||||
<!--logged in users-->
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>${_("What can we help you with, {username}?").format(username=user.username)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
% if user_enrollments:
|
||||
<label class="label-course" for="course">${_("Course Name")}</label>
|
||||
<select class="form-control select-course" id="course">
|
||||
% for enrollment in user_enrollments:
|
||||
<option value="${enrollment.course.id}">${enrollment.course.display_name}</option>
|
||||
% endfor
|
||||
</select>
|
||||
% else:
|
||||
<label for="course">${_("Course Name")}<span> ${_("(Optional)")}</span></label>
|
||||
<input type="text" class="form-control" id="course">
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% endif
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="message">${_("Message")}</label>
|
||||
<p class="message-desc">${_("The more you tell us, themore quickly and helpfully we can respond!")}</p>
|
||||
<textarea aria-describedby="message-desc" class="form-control" rows="7" id="message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<label for="attachment">${_("Add Attachment")}
|
||||
<span>${_("(Optional)")}</span>
|
||||
</label>
|
||||
<input id="attachment" multiple type="file" class="file file-loading" data-allowed-file-extensions='["png", "jpg", "gif", "tif", "jpeg"]'>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<p>${_("1 file uploaded:")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<span class="file-name">my_image1.png</span>
|
||||
<span class="file-action"><a href="#">${_("Remove file")}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<span class="file-name">my_image2.png</span>
|
||||
<span class="file-action"><a href="#">${_("Cancel upload")}</a></span>
|
||||
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped" role="progressbar"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<button class="btn btn-primary">${_("Submit")}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
new SingleSupportForm(context);
|
||||
</%static:webpack>
|
||||
</%block>
|
||||
|
||||
@@ -28,6 +28,9 @@ var wpconfig = {
|
||||
Import: './cms/static/js/features/import/factories/import.js',
|
||||
StudioIndex: './cms/static/js/features_jsx/studio/index.jsx',
|
||||
|
||||
// LMS: single support form
|
||||
SingleSupportForm: './lms/static/support/jsx/single_support_form.jsx',
|
||||
|
||||
// Features
|
||||
CourseGoals: './openedx/features/course_experience/static/course_experience/js/CourseGoals.js',
|
||||
CourseHome: './openedx/features/course_experience/static/course_experience/js/CourseHome.js',
|
||||
|
||||
Reference in New Issue
Block a user