Skip to content

Commit

Permalink
Merge pull request #1157 from rdmorganiser/interview-contact-modal
Browse files Browse the repository at this point in the history
feat(interview): Contact modal [4]
  • Loading branch information
jochenklar authored Jan 23, 2025
2 parents 6fb05a5 + 459f656 commit ad6b993
Show file tree
Hide file tree
Showing 27 changed files with 678 additions and 17 deletions.
6 changes: 5 additions & 1 deletion rdmo/core/assets/js/api/BaseApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ class BaseApi {
throw new ApiError(error.message)
}).then(response => {
if (response.ok) {
return response.json()
if (response.status == 204) {
return null
} else {
return response.json()
}
} else if (response.status == 400) {
return response.json().then(errors => {
throw new ValidationError(errors)
Expand Down
1 change: 1 addition & 0 deletions rdmo/core/assets/scss/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ $icon-font-path: "bootstrap-sass/assets/fonts/bootstrap/";
@import 'codemirror';
@import 'fonts';
@import 'footer';
@import 'forms';
@import 'header';
@import 'swagger';
@import 'utils';
3 changes: 3 additions & 0 deletions rdmo/core/assets/scss/forms.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
textarea {
resize: vertical;
}
9 changes: 7 additions & 2 deletions rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,15 @@
'MULTISITE',
'GROUPS',
'EXPORT_FORMATS',
'PROJECT_TABLE_PAGE_SIZE'
'PROJECT_TABLE_PAGE_SIZE',
'PROJECT_CONTACT'
]

TEMPLATES_API = [
'projects/project_interview_add_set_help.html',
'projects/project_interview_add_value_help.html',
'projects/project_interview_buttons_help.html',
'projects/project_interview_contact_help.html',
'projects/project_interview_done.html',
'projects/project_interview_error.html',
'projects/project_interview_multiple_values_warning.html',
Expand All @@ -236,7 +238,7 @@
'projects/project_interview_page_tabs_help.html',
'projects/project_interview_progress_help.html',
'projects/project_interview_question_help.html',
'projects/project_interview_questionset_help.html',
'projects/project_interview_questionset_help.html'
]

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Expand Down Expand Up @@ -305,6 +307,9 @@

PROJECT_VIEWS = True

PROJECT_CONTACT = False
PROJECT_CONTACT_RECIPIENTS = []

PROJECT_EXPORTS = [
('xml', _('RDMO XML'), 'rdmo.projects.exports.RDMOXMLExport'),
('csvcomma', _('CSV (comma separated)'), 'rdmo.projects.exports.CSVCommaExport'),
Expand Down
4 changes: 4 additions & 0 deletions rdmo/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,3 +418,7 @@ def save_metadata(metadata):
f = open(tmp_metadata_file)
log.info('Save metadata file %s %s', tmp_metadata_file, str(metadata))
return tmp_metadata_file


def remove_double_newlines(string):
return re.sub(r'[\n]{2,}', '\n\n', string)
10 changes: 10 additions & 0 deletions rdmo/projects/assets/js/interview/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,13 @@ export const DELETE_SET_ERROR = 'DELETE_SET_ERROR'
export const COPY_SET_INIT = 'COPY_SET_INIT'
export const COPY_SET_SUCCESS = 'COPY_SET_SUCCESS'
export const COPY_SET_ERROR = 'COPY_SET_ERROR'

export const FETCH_CONTACT_INIT = 'FETCH_CONTACT_INIT'
export const FETCH_CONTACT_SUCCESS = 'FETCH_CONTACT_SUCCESS'
export const FETCH_CONTACT_ERROR = 'FETCH_CONTACT_ERROR'

export const SEND_CONTACT_INIT = 'SEND_CONTACT_INIT'
export const SEND_CONTACT_SUCCESS = 'SEND_CONTACT_SUCCESS'
export const SEND_CONTACT_ERROR = 'SEND_CONTACT_ERROR'

export const CLOSE_CONTACT = 'CLOSE_CONTACT'
84 changes: 84 additions & 0 deletions rdmo/projects/assets/js/interview/actions/contactActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import ContactApi from '../api/ContactApi'

import { projectId } from '../utils/meta'

import {
FETCH_CONTACT_INIT,
FETCH_CONTACT_SUCCESS,
FETCH_CONTACT_ERROR,
SEND_CONTACT_INIT,
SEND_CONTACT_SUCCESS,
SEND_CONTACT_ERROR,
CLOSE_CONTACT
} from './actionTypes'

import { addToPending, removeFromPending } from 'rdmo/core/assets/js/actions/pendingActions'

export function fetchContact({ questionset, question, values }) {
const pendingId = 'fetchContact'

return (dispatch, getState) => {
const params = {
page: getState().interview.page.id,
questionset: questionset && questionset.id,
question: question && question.id,
values: values.filter(value => value.id).map(value => value.id)
}

dispatch(addToPending(pendingId))
dispatch(fetchContactInit())

return ContactApi.fetchContact(projectId, params).then((values) => {
dispatch(removeFromPending(pendingId))
dispatch(fetchContactSuccess(values))
}).catch((error) => {
dispatch(removeFromPending(pendingId))
dispatch(fetchContactError(error))
})
}
}

export function fetchContactInit() {
return {type: FETCH_CONTACT_INIT}
}

export function fetchContactSuccess(values) {
return {type: FETCH_CONTACT_SUCCESS, values}
}

export function fetchContactError(error) {
return {type: FETCH_CONTACT_ERROR, error}
}

export function sendContact(values) {
const pendingId = 'sendContact'

return (dispatch) => {
dispatch(addToPending(pendingId))
dispatch(sendContactInit())

return ContactApi.sendContact(projectId, values).then(() => {
dispatch(removeFromPending(pendingId))
dispatch(sendContactSuccess())
}).catch((error) => {
dispatch(removeFromPending(pendingId))
dispatch(sendContactError(error))
})
}
}

export function sendContactInit() {
return {type: SEND_CONTACT_INIT}
}

export function sendContactSuccess() {
return {type: SEND_CONTACT_SUCCESS}
}

export function sendContactError(error) {
return {type: SEND_CONTACT_ERROR, error}
}

export function closeContact() {
return {type: CLOSE_CONTACT}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export function fetchPageError(error) {
}

export function fetchNavigation(page) {
const pendingId = `fetchNavigation/${page.id}`
const pendingId = `fetchNavigation/${page ? page.id : 'done'}`

return (dispatch) => {
dispatch(addToPending(pendingId))
Expand Down
17 changes: 17 additions & 0 deletions rdmo/projects/assets/js/interview/api/ContactApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { encodeParams } from 'rdmo/core/assets/js/utils/api'

import BaseApi from 'rdmo/core/assets/js/api/BaseApi'

class ContactApi extends BaseApi {

static fetchContact(projectId, params) {
return this.get(`/api/v1/projects/projects/${projectId}/contact/?${encodeParams(params)}`)
}

static sendContact(projectId, data) {
return this.post(`/api/v1/projects/projects/${projectId}/contact/`, data)
}

}

export default ContactApi
82 changes: 82 additions & 0 deletions rdmo/projects/assets/js/interview/components/main/Contact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState, useEffect } from 'react'
import PropTypes from 'prop-types'

import Modal from 'rdmo/core/assets/js/components/Modal'

import Html from 'rdmo/core/assets/js/components/Html'

const Contact = ({ templates, contact, sendContact, closeContact }) => {
const { showModal, values: initialValues, errors } = contact

const [values, setValues] = useState({})

useEffect(() => setValues(initialValues), [initialValues])

const onSubmit = (event) => {
event.preventDefault()
sendContact(values)
}

const onClose = () => closeContact()

return (
<>
<Modal
show={showModal}
title={gettext('Contact support')}
submitLabel={gettext('Send message')}
onSubmit={onSubmit}
onClose={onClose}
modalProps={{
bsSize: 'lg'
}}>
<Html html={templates.project_interview_contact_help} />
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="interview-question-contact-subject">Subject</label>
<input
type="text"
id="interview-question-contact-subject"
className="form-control"
value={values.subject || ''}
onChange={event => setValues({ ...values, subject: event.target.value })}
/>
<ul className="help-block list-unstyled">
{
errors && errors.subject && errors.subject.map((error, errorIndex) => (
<li key={errorIndex} className="text-danger">{errors.subject}</li>
))
}
</ul>
</div>
<div className="form-group">
<label htmlFor="interview-question-contact-message">Message</label>
<textarea
rows="12"
id="interview-question-contact-message"
className="form-control"
value={values.message || ''}
onChange={event => setValues({...values, message: event.target.value })}
/>
<ul className="help-block list-unstyled">
{
errors && errors.message && errors.message.map((error, errorIndex) => (
<li key={errorIndex} className="text-danger">{errors.message}</li>
))
}
</ul>
</div>
</form>
</Modal>
</>
)
}

Contact.propTypes = {
templates: PropTypes.object.isRequired,
contact: PropTypes.object.isRequired,
sendContact: PropTypes.func.isRequired,
closeContact: PropTypes.func.isRequired
}

export default Contact
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import QuestionSet from '../questionset/QuestionSet'
import PageButtons from './PageButtons'
import PageHead from './PageHead'

const Page = ({ config, templates, overview, page, sets, values, fetchPage,
const Page = ({ config, settings, templates, overview, page, sets, values, fetchPage, fetchContact,
createValue, updateValue, deleteValue, copyValue,
activateSet, createSet, updateSet, deleteSet, copySet }) => {

Expand Down Expand Up @@ -51,6 +51,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
return (
<QuestionSet
key={elementIndex}
settings={settings}
templates={templates}
questionset={element}
sets={sets}
Expand All @@ -66,12 +67,14 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
fetchContact={fetchContact}
/>
)
} else {
return (
<Question
key={elementIndex}
settings={settings}
templates={templates}
question={element}
sets={sets.filter((set) => (
Expand All @@ -94,6 +97,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
fetchContact={fetchContact}
/>
)
}
Expand All @@ -109,12 +113,14 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,

Page.propTypes = {
config: PropTypes.object.isRequired,
settings: PropTypes.object.isRequired,
templates: PropTypes.object.isRequired,
overview: PropTypes.object.isRequired,
page: PropTypes.object.isRequired,
sets: PropTypes.array.isRequired,
values: PropTypes.array.isRequired,
fetchPage: PropTypes.func.isRequired,
fetchContact: PropTypes.func.isRequired,
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
import { checkQuestion } from '../../../utils/page'

import QuestionAddValueHelp from './QuestionAddValueHelp'
import QuestionContact from './QuestionContact'
import QuestionHelp from './QuestionHelp'
import QuestionHelpTemplate from './QuestionHelpTemplate'
import QuestionManagement from './QuestionManagement'
Expand All @@ -12,10 +13,11 @@ import QuestionText from './QuestionText'
import QuestionWarning from './QuestionWarning'
import QuestionWidget from './QuestionWidget'

const Question = ({ templates, question, sets, values, siblings, disabled, isManager,
currentSet, createValue, updateValue, deleteValue, copyValue }) => {
const Question = ({ settings, templates, question, sets, values, siblings, disabled, isManager,
currentSet, createValue, updateValue, deleteValue, copyValue, fetchContact }) => {
return checkQuestion(question, currentSet) && (
<div className={`interview-question col-md-${question.width || '12'}`}>
<QuestionContact settings={settings} question={question} values={values} fetchContact={fetchContact} />
<QuestionOptional question={question} />
<QuestionText question={question} />
<QuestionHelp question={question} />
Expand All @@ -40,6 +42,7 @@ const Question = ({ templates, question, sets, values, siblings, disabled, isMan
}

Question.propTypes = {
settings: PropTypes.object.isRequired,
templates: PropTypes.object.isRequired,
question: PropTypes.object.isRequired,
sets: PropTypes.array.isRequired,
Expand All @@ -51,7 +54,8 @@ Question.propTypes = {
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired
copyValue: PropTypes.func.isRequired,
fetchContact: PropTypes.func.isRequired
}

export default Question
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import PropTypes from 'prop-types'

const QuestionContact = ({ settings, question, values, fetchContact }) => {
return settings.project_contact && (
<button className="btn btn-link btn-contact" title={gettext('Contact support.')}
onClick={() => fetchContact({ question, values })}>
<i className="fa fa-commenting-o" aria-hidden="true"></i>
</button>
)
}

QuestionContact.propTypes = {
settings: PropTypes.object.isRequired,
question: PropTypes.object.isRequired,
values: PropTypes.array.isRequired,
fetchContact: PropTypes.func.isRequired,
}

export default QuestionContact
Loading

0 comments on commit ad6b993

Please sign in to comment.