diff --git a/zubhub_backend/zubhub/projects/views.py b/zubhub_backend/zubhub/projects/views.py index 72ce4f044..6d202fbe0 100644 --- a/zubhub_backend/zubhub/projects/views.py +++ b/zubhub_backend/zubhub/projects/views.py @@ -26,6 +26,7 @@ class ProjectUpdateAPIView(UpdateAPIView): def perform_update(self, serializer): serializer.save(creator=self.request.user) + self.request.user.save() class ProjectDeleteAPIView(DestroyAPIView): diff --git a/zubhub_frontend/zubhub/public/locales/en/translation.json b/zubhub_frontend/zubhub/public/locales/en/translation.json index 80dea64f3..3a275dd93 100644 --- a/zubhub_frontend/zubhub/public/locales/en/translation.json +++ b/zubhub_frontend/zubhub/public/locales/en/translation.json @@ -203,14 +203,14 @@ }, "inputs": { "title": { - "label": "Title", + "label": "Name your project", "errors": { "max": "your project title shouldn't be more than 100 characters", "required": "this field is required" } }, "description": { - "label": "Description", + "label": "Describe what it is", "helperText": "Tell us something interesting about the project! You can share what it is about, what inspired you to make it, your making process, fun and challenging moments you experienced, etc.", "errors": { "max": "your description shouldn't be more than 10,000 characters", @@ -218,7 +218,9 @@ } }, "projectImages": { - "label": "Images", + "label": "Let's add some pictures", + "label2": "Add Images", + "topHelperText": "Don't have them? add a video instead", "errors": { "onlyImages": "only images are allowed in this field", "imageOrVideo": "you must provide either image(s) or video url", @@ -227,8 +229,9 @@ } }, "video": { - "label": "Video URL", - "helperText": "YouTube, Vimeo, Google Drive links are supported", + "label": "Let's add a video", + "topHelperText": "It's ok if you don't have them", + "bottomHelperText": "YouTube, Vimeo, Google Drive links are supported", "errors": { "shouldBeVideoUrl": "you are required to submit a video url here", "max": "you are required to submit a video url here", @@ -236,15 +239,17 @@ } }, "materialsUsed": { - "label": "Materials Used", - "placeholder": "Add a material and click the + button", + "label": "What materials did you use?", + "addMore": "Add More", "errors": { "max": "you are required to submit a video url here", "required": "this field is required" } }, "submit": "Create Project", - "edit": "Edit Project" + "edit": "Edit Project", + "image": "image added", + "images": "images added" }, "createToastSuccess": "Your project was created successfully!!", "updateToastSuccess": "Your project was updated successfully!!", diff --git a/zubhub_frontend/zubhub/public/locales/hi/translation.json b/zubhub_frontend/zubhub/public/locales/hi/translation.json index d046ef792..e718bf51a 100644 --- a/zubhub_frontend/zubhub/public/locales/hi/translation.json +++ b/zubhub_frontend/zubhub/public/locales/hi/translation.json @@ -205,14 +205,14 @@ }, "inputs": { "title": { - "label": "शीर्षक", + "label": "अपने प्रोजेक्ट का नाम बताइए", "errors": { "max": "आपकी परियोजना का शीर्षक 100 से अधिक वर्ण नहीं होना चाहिए", "required": "यह फ़ील्ड आवश्यक है" } }, "description": { - "label": "विवरण", + "label": "वर्णन करें कि यह क्या है", "helperText": "प्रोजेक्ट के बारे में कुछ दिलचस्प बताएं! आप इसे साझा कर सकते हैं कि यह किस बारे में है, आपको इसे बनाने के लिए क्या प्रेरणा मिली है, आपकी बनाने की प्रक्रिया, आपके द्वारा अनुभव किए गए मजेदार और चुनौतीपूर्ण क्षण आदि।", "errors": { "max": "आपका वर्णन 10,000 से अधिक वर्णों का नहीं होना चाहिए", @@ -220,7 +220,9 @@ } }, "projectImages": { - "label": "इमेजिस", + "label": "आइए कुछ तस्वीरें जोड़ते हैं", + "label2": "छवियां जोड़ें", + "topHelperText": "उनके पास नहीं है? इसके बजाय एक वीडियो जोड़ें", "errors": { "onlyImages": "इस क्षेत्र में केवल छवियों की अनुमति है", "imageOrVideo": "आपको छवि या वीडियो url प्रदान करना चाहिए", @@ -229,8 +231,9 @@ } }, "video": { - "label": "वीडियो यूआरएल", - "helperText": "YouTube, Vimeo, Google Drive लिंक समर्थित हैं", + "label": "आइए एक वीडियो जोड़ें", + "topHelperText": "यह ठीक है अगर आपके पास उन्हें नहीं है", + "bottomHelperText": "YouTube, Vimeo, Google Drive लिंक समर्थित हैं", "errors": { "shouldBeVideoUrl": "आपको यहां एक वीडियो url प्रस्तुत करना आवश्यक है", "max": "आपको यहां एक वीडियो url प्रस्तुत करना आवश्यक है", @@ -238,15 +241,17 @@ } }, "materialsUsed": { - "label": "उपयोग किया गया सामन", - "placeholder": "एक सामग्री जोड़ें और + बटन पर क्लिक करें", + "label": "आपने किन सामग्रियों का उपयोग किया?", + "addMore": "अधिक जोड़ें", "errors": { "max": "आपके द्वारा उपयोग की जाने वाली सामग्री 10,000 से अधिक वर्णों की नहीं होनी चाहिए", "required": "यह फ़ील्ड आवश्यक है" } }, "submit": "प्रोजेक्ट बनाएं", - "edit": "प्रोजेक्ट संपादित करें" + "edit": "प्रोजेक्ट संपादित करें", + "image": "छवि जोड़ी गई", + "images": "चित्र जोड़े गए" }, "createToastSuccess": "आपका प्रोजेक्ट सफलतापूर्वक बनाया गया था !!", "updateToastSuccess": "आपका प्रोजेक्ट सफलतापूर्वक अपडेट किया गया था !!", diff --git a/zubhub_frontend/zubhub/src/api/api.js b/zubhub_frontend/zubhub/src/api/api.js index 5af60d08c..acc21e023 100644 --- a/zubhub_frontend/zubhub/src/api/api.js +++ b/zubhub_frontend/zubhub/src/api/api.js @@ -65,7 +65,6 @@ class API { const url = 'rest-auth/login/'; const method = 'POST'; const body = JSON.stringify({ username, password }); - console.log('stringified json', body); return this.request({ url, method, body }).then(res => res.json()); }; diff --git a/zubhub_frontend/zubhub/src/assets/js/styles/index.js b/zubhub_frontend/zubhub/src/assets/js/styles/index.js index 1d983a2d6..32cd78e74 100644 --- a/zubhub_frontend/zubhub/src/assets/js/styles/index.js +++ b/zubhub_frontend/zubhub/src/assets/js/styles/index.js @@ -1,10 +1,22 @@ const styles = theme => ({ + marginTop1em: { + marginTop: '1em', + }, + marginBottom1em: { + marginBottom: '1em', + }, marginLeft1em: { marginLeft: '1em', }, colorRed: { color: 'red', }, + topMinus20PX: { + top: '-20px', + }, + left15PX: { + left: '15px', + }, }); export default styles; diff --git a/zubhub_frontend/zubhub/src/assets/js/styles/views/create_project/createProjectStyles.js b/zubhub_frontend/zubhub/src/assets/js/styles/views/create_project/createProjectStyles.js index 4fca2a7b2..905a87799 100644 --- a/zubhub_frontend/zubhub/src/assets/js/styles/views/create_project/createProjectStyles.js +++ b/zubhub_frontend/zubhub/src/assets/js/styles/views/create_project/createProjectStyles.js @@ -23,6 +23,8 @@ const styles = theme => ({ '&.MuiFormLabel-root.Mui-focused': { color: '#00B8C4', }, + fontWeight: 'bold', + fontSize: '1rem !important', }, customInputStyle: { @@ -43,15 +45,21 @@ const styles = theme => ({ }, }, }, - staticLabelInputStyle: { - '&.MuiOutlinedInput-root fieldset legend': { - width: '75.5px !important', - }, - }, - staticLabelInputSmallStyle: { - '&.MuiOutlinedInput-root fieldset legend': { - width: '40px !important', - }, + fieldNumberStyle: { + height: '20px', + width: '20px', + backgroundColor: '#00B8C4', + color: 'white', + display: 'inline-block', + textAlign: 'center', + borderRadius: '50%', + lineHeight: '17px', + marginRight: '0.3em', + }, + questionIconStyle: { + position: 'absolute', + color: '#00B8C4', + right: '10px', }, uploadProgressLabelStyle: { color: 'white', diff --git a/zubhub_frontend/zubhub/src/views/create_project/CreateProject.jsx b/zubhub_frontend/zubhub/src/views/create_project/CreateProject.jsx index 069fad071..5b4a8658d 100644 --- a/zubhub_frontend/zubhub/src/views/create_project/CreateProject.jsx +++ b/zubhub_frontend/zubhub/src/views/create_project/CreateProject.jsx @@ -13,6 +13,7 @@ import { nanoid } from 'nanoid'; import { makeStyles } from '@material-ui/core/styles'; import AddIcon from '@material-ui/icons/Add'; import ImageIcon from '@material-ui/icons/Image'; +import HelpIcon from '@material-ui/icons/Help'; import { Grid, Box, @@ -21,13 +22,13 @@ import { CardActionArea, CardContent, Dialog, - Chip, Typography, CircularProgress, OutlinedInput, - InputLabel, FormHelperText, FormControl, + Tooltip, + ClickAwayListener, } from '@material-ui/core'; import * as ProjectActions from '../../store/actions/projectActions'; @@ -38,8 +39,10 @@ import Compress from '../../assets/js/Compress'; import { useStateUpdateCallback } from '../../assets/js/customHooks'; import CustomButton from '../../components/button/Button'; import styles from '../../assets/js/styles/views/create_project/createProjectStyles'; +import commonStyles from '../../assets/js/styles'; const useStyles = makeStyles(styles); +const useCommonStyles = makeStyles(commonStyles); let image_field_touched = false; let video_field_touched = false; @@ -56,28 +59,37 @@ const getProject = (refs, props, state) => { } else { const { image_upload } = state; - props.setFieldValue('title', obj.project.title); - if (refs.titleEl.current) + if (refs.titleEl.current && obj.project.title) { + props.setFieldValue('title', obj.project.title); refs.titleEl.current.firstChild.value = obj.project.title; + } - props.setFieldValue('description', obj.project.description); - if (refs.descEl.current) + if (refs.descEl.current && obj.project.description) { + props.setFieldValue('description', obj.project.description); refs.descEl.current.firstChild.value = obj.project.description; + } - if (obj.project.video) { + if (refs.videoEl.current && obj.project.video) { props.setFieldValue('video', obj.project.video); - if (refs.videoEl.current) - refs.videoEl.current.firstChild.value = obj.project.video; + refs.videoEl.current.firstChild.value = obj.project.video; } - if (refs.imageCountEl.current) { - refs.imageCountEl.current.innerText = obj.project.images.length; - refs.imageCountEl.current.style.fontSize = '0.8rem'; + if (refs.imageCountEl.current && obj.project.images.length > 0) { + refs.imageCountEl.current.innerText = `${ + obj.project.images.length + } ${props.t( + `createProject.inputs.${ + obj.project.images.length < 2 ? 'image' : 'images' + }`, + )}`; } - if (refs.materialsUsedEl.current) { - props.setFieldValue('materials_used', obj.project.materials_used); - refs.materialsUsedEl.current.value = obj.project.materials_used; + if (refs.addMaterialsUsedEl.current && obj.project.materials_used) { + props.setFieldValue( + 'materials_used', + obj.project.materials_used, + true, + ); } image_upload.uploaded_images_url = obj.project.images; @@ -92,8 +104,13 @@ const getProject = (refs, props, state) => { }; const handleImageFieldChange = (refs, props, state, handleSetState) => { - refs.imageCountEl.current.innerText = refs.imageEl.current.files.length; - refs.imageCountEl.current.style.fontSize = '0.8rem'; + refs.imageCountEl.current.innerText = `${ + refs.imageEl.current.files.length + } ${props.t( + `createProject.inputs.${ + refs.imageEl.current.files.length < 2 ? 'image' : 'images' + }`, + )}`; props.setFieldValue('project_images', refs.imageEl.current).then(errors => { if (!errors['project_images']) { @@ -116,39 +133,73 @@ const handleImageButtonClick = (e, props, refs) => { props.setFieldTouched('project_images'); }; -const removeMaterialsUsed = (e, props, refs, value) => { - e.preventDefault(); - const hidden_materials_field = refs.materialsUsedEl.current; - hidden_materials_field.value = hidden_materials_field.value - .split(',') - .filter(material => material !== value) - .join(','); - - props.setFieldValue('materials_used', hidden_materials_field.value, true); - return { materials_used: hidden_materials_field.value.split(',') }; +const handleDescTooltipOpen = () => { + return { descToolTipOpen: true }; +}; + +const handleDescTooltipClose = () => { + return { descToolTipOpen: false }; }; const handleAddMaterialFieldChange = (e, props, refs) => { - props.validateField('materials_used'); - const value = refs.addMaterialsUsedEl.current.firstChild.value; - if (value.includes(',')) { - refs.addMaterialsUsedEl.current.firstChild.value = value.split(',')[0]; - return addMaterialUsed(e, props, refs); + const children = refs.addMaterialsUsedEl.current.children; + let value = ''; + for (let index = 0; index < children.length; index++) { + if (children[index].children[0].value) { + if (index < 1) { + value = value.concat(children[index].children[0].value); + } else { + value = value.concat(`,${children[index].children[0].value}`); + } + } else { + if (index >= 1) { + value = value.concat(','); + } + } } + + props.setFieldValue('materials_used', value, true); }; -const addMaterialUsed = (e, props, refs) => { +const BuildMaterialUsedNodes = (props, refs) => { + const classes = useStyles(); + const commonClasses = useCommonStyles(); + + if (props.values['materials_used']) { + return props.values['materials_used'] + .split(',') + .map((material, index) => ( + props.setFieldTouched('materials_used', true)} + onChange={e => handleAddMaterialFieldChange(e, props, refs)} + value={material} + placeholder={`${index + 1}.`} + /> + )); + } else { + return ['', '', ''].map((material, index) => ( + props.setFieldTouched('materials_used', true)} + onChange={e => handleAddMaterialFieldChange(e, props, refs)} + placeholder={`${index + 1}.`} + /> + )); + } +}; + +const addMaterialsUsedNode = (e, props) => { e.preventDefault(); - const new_material = refs.addMaterialsUsedEl.current.firstChild; - if (new_material.value !== '') { - const hidden_materials_field = refs.materialsUsedEl.current; - hidden_materials_field.value = hidden_materials_field.value - ? `${hidden_materials_field.value},${new_material.value}` - : new_material.value; - new_material.value = ''; - props.setFieldValue('materials_used', hidden_materials_field.value, true); - new_material.focus(); - return { materials_used: hidden_materials_field.value.split(',') }; + let materials_used = props.values['materials_used']; + if (!materials_used) { + props.setFieldValue('materials_used', ',,,'); + } else { + props.setFieldValue('materials_used', materials_used.concat(',')); } }; @@ -161,11 +212,12 @@ function CreateProject(props) { imageCountEl: React.useRef(null), videoEl: React.useRef(null), addMaterialsUsedEl: React.useRef(null), - materialsUsedEl: React.useRef(null), }; const classes = useStyles(); + const commonClasses = useCommonStyles(); const [state, setState] = React.useState({ + descToolTipOpen: false, loading: true, error: null, materialsUsedModalOpen: false, @@ -286,12 +338,18 @@ function CreateProject(props) { image_upload.upload_dialog = false; handleSetState({ image_upload }); + const materials_used = props.values['materials_used'] + .split(',') + .filter(value => (value ? true : false)) + .join(','); + const create_or_update = props.match.params.id ? props.update_project : props.create_project; return create_or_update({ ...props.values, + materials_used, id: props.match.params.id, token: props.auth.token, images: state.image_upload.uploaded_images_url, @@ -369,7 +427,7 @@ function CreateProject(props) { } }; - const { image_upload, materials_used } = state; + const { descToolTipOpen, image_upload } = state; const { t } = props; const id = props.match.params.id; if (!props.auth.token) { @@ -424,7 +482,7 @@ function CreateProject(props) { - + - - {t('createProject.inputs.title.label')} - + {(props.status && props.status['title']) || @@ -468,7 +526,7 @@ function CreateProject(props) { - + - + + 2 + {t('createProject.inputs.description.label')} + + + + handleSetState(handleDescTooltipClose()) + } > - {t('createProject.inputs.description.label')} - + + handleSetState(handleDescTooltipClose()) + } + open={descToolTipOpen} + disableFocusListener + disableHoverListener + title={t( + 'createProject.inputs.description.helperText', + )} + > + + handleSetState(handleDescTooltipOpen()) + } + /> + + - - {t('createProject.inputs.description.helperText')} - -
{(props.status && props.status['description']) || (props.touched['description'] && props.errors['description'] && @@ -523,7 +602,7 @@ function CreateProject(props) {
- + + + + {t( + 'createProject.inputs.projectImages.topHelperText', + )} + + + + } + onClick={e => + handleImageButtonClick(e, props, refs) + } + secondaryButtonStyle + imageUploadButtonStyle + fullWidth + > + {t('createProject.inputs.projectImages.label2')} + + + + - + - + + 4 + {t('createProject.inputs.video.label')} + + + - {t('createProject.inputs.video.label')} - + {t('createProject.inputs.video.topHelperText')} + - - {t('createProject.inputs.video.helperText')} - -
{(props.status && props.status['video']) || (props.touched['video'] && props.errors['video'] && @@ -633,10 +741,17 @@ function CreateProject(props) { `createProject.inputs.video.errors.${props.errors['video']}`, ))}
+ + {t('createProject.inputs.video.bottomHelperText')} +
- + - - {t('createProject.inputs.materialsUsed.label')} - - - {materials_used.map((material, num) => - material !== '' ? ( - - handleSetState( - removeMaterialsUsed( - e, - props, - refs, - material, - ), - ) - } - color="secondary" - variant="outlined" - /> - ) : null, - )} - - - - - props.setFieldTouched('materials_used') - } - onChange={e => - handleSetState( - handleAddMaterialFieldChange(e, props, refs), - ) - } - onBlur={() => - props.validateField('materials_used') - } - /> - - {(props.status && - props.status['materials_used']) || - (props.touched['materials_used'] && - props.errors['materials_used'] && - t( - `createProject.inputs.materialsUsed.errors.${props.errors['materials_used']}`, - ))} - + + + + + + {BuildMaterialUsedNodes(props, refs)} + - + - handleSetState(addMaterialUsed(e, props, refs)) - } + onClick={e => addMaterialsUsedNode(e, props)} secondaryButtonStyle fullWidth > - + {' '} + {t('createProject.inputs.materialsUsed.addMore')} + + {(props.status && props.status['materials_used']) || + (props.touched['materials_used'] && + props.errors['materials_used'] && + t( + `createProject.inputs.materialsUsed.errors.${props.errors['materials_used']}`, + ))} + - @@ -890,7 +957,20 @@ export default connect( ? false : true; }), - materials_used: Yup.string().max(10000, 'max').required('required'), + materials_used: Yup.string() + .max(10000, 'max') + .test('empty', 'required', value => { + let is_empty = true; + + value && + value.split(',').forEach(material => { + if (material) { + is_empty = false; + } + }); + + return !is_empty; + }), }), })(CreateProject), );