From 4b601fae1c640e1aeab17b87877e84942b96d172 Mon Sep 17 00:00:00 2001 From: Suchakra Sharma Date: Tue, 31 Oct 2023 12:03:48 -0700 Subject: [PATCH] Revert "(enhancement) Update activities feature flow (#885)" (#986) This reverts commit 1b996f2fdfbfbc35a0860260b81e4445b5e67c27. --- zubhub_backend/zubhub/activities/urls.py | 4 +- zubhub_frontend/zubhub/src/App.js | 55 +- zubhub_frontend/zubhub/src/api/api.js | 143 +- .../zubhub/src/components/Sidenav/data.js | 2 +- .../src/components/activity/activity.jsx | 63 +- .../src/components/form/dropdown/Dropdown.jsx | 2 +- .../form/selectFromPills/SelectFromPills.jsx | 2 +- .../src/store/actions/activityActions.js | 20 +- .../src/views/activities/activities.jsx | 58 +- .../activity_details/ActivityDetailsV2.jsx | 62 +- ...ateEditActivity.jsx => CreateActivity.jsx} | 133 +- .../create_activity/createActivityScripts.js | 86 +- .../src/views/create_activity/script.js | 1302 +++++++++-------- .../src/views/create_activity/step1/Step1.jsx | 2 - .../src/views/create_activity/step2/Step2.jsx | 2 + .../src/views/my_activities/MyActivities.jsx | 111 -- 16 files changed, 1075 insertions(+), 972 deletions(-) rename zubhub_frontend/zubhub/src/views/create_activity/{CreateEditActivity.jsx => CreateActivity.jsx} (68%) delete mode 100644 zubhub_frontend/zubhub/src/views/my_activities/MyActivities.jsx diff --git a/zubhub_backend/zubhub/activities/urls.py b/zubhub_backend/zubhub/activities/urls.py index 6a7dd4d63..927aa0e13 100644 --- a/zubhub_backend/zubhub/activities/urls.py +++ b/zubhub_backend/zubhub/activities/urls.py @@ -5,8 +5,8 @@ urlpatterns = [ path('', PublishedActivitiesAPIView.as_view(), name='index'), - path('un-published', UnPublishedActivitiesAPIView.as_view(), name='unPublished'), - path('my-activities', UserActivitiesAPIView.as_view(), name='myActivities'), + path('unPublished', UnPublishedActivitiesAPIView.as_view(), name='unPublished'), + path('myActivities', UserActivitiesAPIView.as_view(), name='myActivities'), path('create/', ActivityCreateAPIView.as_view(), name='create'), path('/update/', ActivityUpdateAPIView.as_view(), name='update'), path('/delete/', ActivityDeleteAPIView.as_view(), name='delete'), diff --git a/zubhub_frontend/zubhub/src/App.js b/zubhub_frontend/zubhub/src/App.js index d6b1170f1..fd1c23b2c 100644 --- a/zubhub_frontend/zubhub/src/App.js +++ b/zubhub_frontend/zubhub/src/App.js @@ -1,20 +1,20 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { withTranslation } from 'react-i18next'; import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; +import CreateActivity from './views/create_activity/CreateActivity'; +import { useEffect } from 'react'; import { connect } from 'react-redux'; -import { updateTheme } from './theme'; import LoadingPage from './views/loading/LoadingPage'; import PageWrapper from './views/PageWrapper'; import ProtectedRoute from './components/protected_route/ProtectedRoute'; import ZubhubAPI from '../src/api/api'; +import { updateTheme } from './theme'; import ScrollToTop from './ScrollToTop'; const SearchResults = React.lazy(() => import('./views/search_results/SearchResults')); const Signup = React.lazy(() => import('./views/signup/Signup')); const Login = React.lazy(() => import('./views/login/Login')); - -const CreateEditActivity = React.lazy(() => import('./views/create_activity/CreateEditActivity')); const PasswordReset = React.lazy(() => import('./views/password_reset/PasswordReset')); const PasswordResetConfirm = React.lazy(() => import('./views/password_reset_confirm/PasswordResetConfirm')); const EmailConfirm = React.lazy(() => import('./views/email_confirm/EmailConfirm')); @@ -43,7 +43,6 @@ const CreateProject = React.lazy(() => import('./views/create_project/CreateProj const ProjectDetails = React.lazy(() => import('./views/project_details/ProjectDetails')); const StaffPickDetails = React.lazy(() => import('./views/staff_pick_details/StaffPickDetails')); const Activities = React.lazy(() => import('./views/activities/activities')); -const MyActivities = React.lazy(() => import('./views/my_activities/MyActivities')); const ActivityDetails = React.lazy(() => import('./views/activity_details/ActivityDetailsV2')); const CreateTeam = React.lazy(() => import('./views/create_team/CreateTeam')); const LinkedProjects = React.lazy(() => import('./views/linked_projects/LinkedProjects')); @@ -119,13 +118,23 @@ function App(props) { )} /> */} + ( - + + + )} + /> + + ( + + )} /> @@ -448,12 +457,32 @@ function App(props) { )} /> - - - - - - + ( + + + + )} + /> + + ( + + + + )} + /> + + ( + + + + )} + /> } */ - request = ({ url = '/', method = 'GET', token, body, content_type = 'application/json' }) => { + request = ({ + url = '/', + method = 'GET', + token, + body, + content_type = 'application/json', + }) => { if (method === 'GET' && !token) { return fetch(this.domain + url, { method, @@ -46,14 +52,14 @@ class API { withCredentials: 'true', headers: content_type ? new Headers({ - Authorization: `Token ${token}`, - 'Content-Type': content_type, - 'Accept-Language': `${i18next.language},en;q=0.5`, - }) + Authorization: `Token ${token}`, + 'Content-Type': content_type, + 'Accept-Language': `${i18next.language},en;q=0.5`, + }) : new Headers({ - Authorization: `Token ${token}`, - 'Accept-Language': `${i18next.language},en;q=0.5`, - }), + Authorization: `Token ${token}`, + 'Accept-Language': `${i18next.language},en;q=0.5`, + }), body, }); } else if (token) { @@ -132,7 +138,17 @@ class API { * * @todo - describe method's signature */ - signup = ({ username, email, phone, dateOfBirth, user_location, password1, password2, bio, subscribe }) => { + signup = ({ + username, + email, + phone, + dateOfBirth, + user_location, + password1, + password2, + bio, + subscribe, + }) => { const url = 'creators/register/'; const method = 'POST'; const body = JSON.stringify({ @@ -280,7 +296,7 @@ class API { const url = `creators/${groupname}/remove-member/${username}/`; const method = 'DELETE'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -296,7 +312,7 @@ class API { const url = `creators/${groupname}/toggle-follow/${username}/`; const method = 'GET'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -312,7 +328,7 @@ class API { const url = `creators/${groupname}/members/`; const method = 'GET'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -324,7 +340,7 @@ class API { * * @todo - describe method's signature */ - teamMembersId = id => { + teamMembersId = ( id ) => { const url = `creators/id/${id}/`; const method = 'GET'; return this.request({ url, method }).then(res => res.json()); @@ -340,7 +356,7 @@ class API { const url = `creators/${groupname}/delete-group/`; const method = 'DELETE'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -356,7 +372,7 @@ class API { const url = `creators/${groupname}/group-followers/`; const method = 'GET'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -372,7 +388,7 @@ class API { const url = `creators/groups/${username}/`; const method = 'GET'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -388,7 +404,7 @@ class API { const url = `creators/teams/`; const method = 'GET'; if (token) { - return this.request({ url, method, token }).then(res => res.json()); + return this.request({ url, method ,token }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -406,7 +422,7 @@ class API { const content_type = false; const body = data; if (token) { - return this.request({ url, method, token, body, content_type }).then(res => res.json()); + return this.request({ url, method ,token, body, content_type }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -423,7 +439,7 @@ class API { const method = 'POST'; const content_type = 'application/json'; const body = JSON.stringify(data); - + if (token) { return this.request({ url, method, token, body, content_type }).then(res => res.json()); } else { @@ -443,7 +459,7 @@ class API { const content_type = 'application/json'; const body = JSON.stringify(data); if (token) { - return this.request({ url, method, token, body, content_type }).then(res => res.json()); + return this.request({ url, method ,token, body, content_type }).then(res => res.json()); } else { return this.request({ url, method }).then(res => res.json()); } @@ -458,7 +474,7 @@ class API { */ getUserProjects = ({ username, page, limit, token, project_to_omit }) => { let url = `creators/${username}/projects`; - let queryParams = sanitizeObject({ page, limit, project_to_omit }); + let queryParams = sanitizeObject({ page, limit, project_to_omit }) const searchParams = new URLSearchParams(queryParams); url = `${url}?${searchParams}`; return this.request({ url, token }).then(res => res.json()); @@ -558,7 +574,9 @@ class API { * @todo - describe method's signature */ getFollowers = ({ page, username }) => { - const url = page ? `creators/${username}/followers/?${page}` : `creators/${username}/followers/`; + const url = page + ? `creators/${username}/followers/?${page}` + : `creators/${username}/followers/`; return this.request({ url }).then(res => res.json()); }; @@ -570,7 +588,9 @@ class API { * @todo - describe method's signature */ getFollowing = ({ page, username }) => { - const url = page ? `creators/${username}/following/?${page}` : `creators/${username}/following/`; + const url = page + ? `creators/${username}/following/?${page}` + : `creators/${username}/following/`; return this.request({ url }).then(res => res.json()); }; @@ -594,7 +614,8 @@ class API { * @todo - describe method's signature */ editUserProfile = props => { - const { token, username, email, phone, dateOfBirth, bio, user_location } = props; + const { token, username, email, phone, dateOfBirth, bio, user_location } = + props; const url = 'creators/edit-creator/'; const method = 'PUT'; @@ -642,7 +663,9 @@ class API { * @todo - describe method's signature */ getMembers = ({ page, username }) => { - const url = page ? `creators/${username}/members/?${page}` : `creators/${username}/members/`; + const url = page + ? `creators/${username}/members/?${page}` + : `creators/${username}/members/`; return this.request({ url }).then(res => res.json()); }; @@ -658,7 +681,9 @@ class API { const method = 'POST'; const content_type = false; const body = data; - return this.request({ url, method, token, body, content_type }).then(res => res.json()); + return this.request({ url, method, token, body, content_type }).then(res => + res.json(), + ); }; /** @@ -706,7 +731,18 @@ class API { * * @todo - describe method's signature */ - createProject = ({ token, title, description, video, images, materials_used, category, tags, publish, activity }) => { + createProject = ({ + token, + title, + description, + video, + images, + materials_used, + category, + tags, + publish, + activity, + }) => { const url = 'projects/create/'; const method = 'POST'; const body = JSON.stringify({ @@ -729,7 +765,18 @@ class API { * * @todo - describe method's signature */ - updateProject = ({ token, id, title, description, video, images, materials_used, category, tags, publish }) => { + updateProject = ({ + token, + id, + title, + description, + video, + images, + materials_used, + category, + tags, + publish, + }) => { const url = `projects/${id}/update/`; const method = 'PATCH'; @@ -783,7 +830,9 @@ class API { const method = 'PATCH'; const body = JSON.stringify({}); return this.request({ url, method, token, body }).then(res => - Promise.resolve(res.status === 200 ? res.json() : { details: 'unknown error' }), + Promise.resolve( + res.status === 200 ? res.json() : { details: 'unknown error' }, + ), ); }; @@ -813,11 +862,11 @@ class API { }; /** - * @method getActivity - * @author Yaya Mamoudou - * - * @todo - describe method's signature - */ + * @method getActivity + * @author Yaya Mamoudou + * + * @todo - describe method's signature + */ getActivity = ({ token, id }) => { const url = `activities/${id}`; return this.request({ token, url }).then(res => res.json()); @@ -863,7 +912,9 @@ class API { * @todo - describe method's signature */ getStaffPick = ({ token, page, id }) => { - const url = page ? `projects/staff-picks/${id}/?page=${page}` : `projects/staff-picks/${id}`; + const url = page + ? `projects/staff-picks/${id}/?page=${page}` + : `projects/staff-picks/${id}`; return this.request({ token, url }).then(res => res.json()); }; @@ -973,10 +1024,10 @@ class API { }; /** - * @method getChallenge - * @author Suchakra Sharma - * - */ + * @method getChallenge + * @author Suchakra Sharma + * + */ getChallenge = () => { const url = `challenge/`; @@ -1014,7 +1065,9 @@ class API { * @todo - describe method's signature */ getAmbassadors = ({ token, page }) => { - const url = page ? `ambassadors/?page=${page}` : `ambassadors`; + const url = page + ? `ambassadors/?page=${page}` + : `ambassadors`; return this.request({ token, url }).then(res => res.json()); }; @@ -1074,6 +1127,7 @@ class API { const body = JSON.stringify(args); return this.request({ url, method, token, body }); + //.then(res => res.json()); }; deleteActivity = ({ token, id }) => { @@ -1082,8 +1136,9 @@ class API { return this.request({ url, method, token }); }; - getActivities = params => { - let queryParams = sanitizeObject(params); + getActivities = (params) => { + + let queryParams = sanitizeObject(params) const searchParams = new URLSearchParams(queryParams); let url = `activities`; url = `${url}?${searchParams}`; @@ -1092,12 +1147,12 @@ class API { }; getUnPublishedActivities = token => { - const url = 'activities/un-published'; + const url = 'activities/unPublished'; return this.request({ url, method: 'GET', token }); }; getMyActivities = token => { - const url = 'activities/my-activities'; + const url = 'activities/myActivities'; return this.request({ url, method: 'GET', token }); }; diff --git a/zubhub_frontend/zubhub/src/components/Sidenav/data.js b/zubhub_frontend/zubhub/src/components/Sidenav/data.js index 9b0d90438..fa087b16b 100644 --- a/zubhub_frontend/zubhub/src/components/Sidenav/data.js +++ b/zubhub_frontend/zubhub/src/components/Sidenav/data.js @@ -17,7 +17,7 @@ export const links = ({ draftCount, myProjectCount, auth, t }) => [ { label: t('pageWrapper.sidebar.projects'), link: '/', icon: Dashboard }, { label: t('pageWrapper.sidebar.profile'), link: '/profile', icon: Person, reactIcon: true, requireAuth: true }, { label: t('pageWrapper.sidebar.createProject'), link: '/projects/create', icon: EmojiObjects }, - ...(['staff', 'educator'].some(tag => auth?.tags.includes(tag)) + ...(auth?.tags.includes('staff') ? [{ label: t('pageWrapper.sidebar.createActivity'), link: '/activities/create', icon: PostAddOutlined }] : []), { diff --git a/zubhub_frontend/zubhub/src/components/activity/activity.jsx b/zubhub_frontend/zubhub/src/components/activity/activity.jsx index 299b0889e..1396b043a 100644 --- a/zubhub_frontend/zubhub/src/components/activity/activity.jsx +++ b/zubhub_frontend/zubhub/src/components/activity/activity.jsx @@ -7,7 +7,11 @@ import { makeStyles } from '@material-ui/core/styles'; import { style } from '../../assets/js/styles/components/activity/activityStyle'; import BookmarkIcon from '@material-ui/icons/Bookmark'; import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder'; -import { getActivities, activityToggleSave, setActivity } from '../../store/actions/activityActions'; +import { + getActivities, + activityToggleSave, + setActivity, +} from '../../store/actions/activityActions'; import { Card, CardActions, @@ -30,7 +34,6 @@ const useCommonStyles = makeStyles(commonStyles); const useStyles = makeStyles(style); function Activity(props) { const { activity, t } = { ...props }; - const [tagsShowMore, setTagsShowMore] = useState(false); const classes = useStyles(); const common_classes = useCommonStyles(); @@ -41,7 +44,11 @@ function Activity(props) {
{activity.creators?.length > 0 ? activity.creators.map((creator, index) => ( - + )) : ''} {activity.tags?.length > 0 ? activity.tags.slice(0, 3).map(tag => ( - + {tag.name} )) @@ -72,7 +86,11 @@ function Activity(props) { onMouseOut={() => setTagsShowMore(false)} > {['+', activity.tags.length - 3].join('')} @@ -116,7 +134,15 @@ function Activity(props) { size="small" aria-label="save button" onClick={e => - toggleSave(e, activity.id, props.auth, props.history, handleSetState, props.activityToggleSave, t) + toggleSave( + e, + activity.id, + props.auth, + props.history, + handleSetState, + props.activityToggleSave, + t, + ) } > {props.auth && activity.saved_by.includes(props.auth.id) ? ( @@ -125,21 +151,36 @@ function Activity(props) { )} - + - + {activity.title} props.setActivity(activity)} > - + {`${t('activities.LinkedProjects')} `}{' '} - {` ${activity.inspired_projects.length}`} + {` ${activity.inspired_projects.length}`} diff --git a/zubhub_frontend/zubhub/src/components/form/dropdown/Dropdown.jsx b/zubhub_frontend/zubhub/src/components/form/dropdown/Dropdown.jsx index c1319ea76..4a9df307f 100644 --- a/zubhub_frontend/zubhub/src/components/form/dropdown/Dropdown.jsx +++ b/zubhub_frontend/zubhub/src/components/form/dropdown/Dropdown.jsx @@ -79,7 +79,7 @@ export default function Dropdown({ {helperText && ( - + {helperText} )} diff --git a/zubhub_frontend/zubhub/src/components/form/selectFromPills/SelectFromPills.jsx b/zubhub_frontend/zubhub/src/components/form/selectFromPills/SelectFromPills.jsx index da8f336a6..c2f551a25 100644 --- a/zubhub_frontend/zubhub/src/components/form/selectFromPills/SelectFromPills.jsx +++ b/zubhub_frontend/zubhub/src/components/form/selectFromPills/SelectFromPills.jsx @@ -44,7 +44,7 @@ export default function SelectFromPills({ return ( {label} - + {helperText} diff --git a/zubhub_frontend/zubhub/src/store/actions/activityActions.js b/zubhub_frontend/zubhub/src/store/actions/activityActions.js index 6a5039856..4e1498b25 100644 --- a/zubhub_frontend/zubhub/src/store/actions/activityActions.js +++ b/zubhub_frontend/zubhub/src/store/actions/activityActions.js @@ -23,12 +23,16 @@ export const setActivity = activity => { }; export const getMyActivities = ({ t, token }) => { + console.log('getMyActivities', token); return async dispatch => { return ActivityAPI.getMyActivities(token) .then(res => { + console.log('result', res); + if (res.status >= 200 && res.status < 300) { let response = res.json(); response.then(all => { + console.log('all', all); dispatch({ type: at.SET_ACTIVITIES, payload: { @@ -38,7 +42,9 @@ export const getMyActivities = ({ t, token }) => { }); } else { if (res.status === 403 && res.statusText === 'Forbidden') { - toast.warning(t('activityDetails.activity.delete.dialog.forbidden')); + toast.warning( + t('activityDetails.activity.delete.dialog.forbidden'), + ); } else { toast.warning(t('activities.errors.dialog.serverError')); } @@ -51,12 +57,16 @@ export const getMyActivities = ({ t, token }) => { }; export const getUnPublishedActivities = ({ t, token }) => { + console.log('getUnPublishedActivities', token); return async dispatch => { return ActivityAPI.getUnPublishedActivities(token) .then(res => { + console.log('result', res); + if (res.status >= 200 && res.status < 300) { let response = res.json(); response.then(all => { + console.log('all', all); dispatch({ type: at.SET_ACTIVITIES, payload: { @@ -66,7 +76,9 @@ export const getUnPublishedActivities = ({ t, token }) => { }); } else { if (res.status === 403 && res.statusText === 'Forbidden') { - toast.warning(t('activityDetails.activity.delete.dialog.forbidden')); + toast.warning( + t('activityDetails.activity.delete.dialog.forbidden'), + ); } else { toast.warning(t('activities.errors.dialog.serverError')); } @@ -94,7 +106,9 @@ export const getActivities = t => { }); } else { if (res.status === 403 && res.statusText === 'Forbidden') { - toast.warning(t('activityDetails.activity.delete.dialog.forbidden')); + toast.warning( + t('activityDetails.activity.delete.dialog.forbidden'), + ); } else { toast.warning(t('activities.errors.dialog.serverError')); } diff --git a/zubhub_frontend/zubhub/src/views/activities/activities.jsx b/zubhub_frontend/zubhub/src/views/activities/activities.jsx index fb1f2fd7f..a4c4605ed 100644 --- a/zubhub_frontend/zubhub/src/views/activities/activities.jsx +++ b/zubhub_frontend/zubhub/src/views/activities/activities.jsx @@ -1,8 +1,9 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { connect, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { getActivities, + getMyActivities, activityToggleSave, setActivity, getUnPublishedActivities, @@ -23,39 +24,35 @@ function Activities(props) { const classes = useStyles(); const [loading, setLoading] = useState(true); const { activities } = useSelector(state => state); - const [activityList, setActivityList] = useState(props?.activities ?? []); + let [activityList, setActivityList] = useState([]); const commonClasses = makeStyles(DefaultStyles)(); - const flagMap = useMemo( - () => ({ - staff: () => - props.getUnPublishedActivities({ - t: props.t, - token: props.auth.token, - }), - educator: () => - props.getMyActivities({ - t: props.t, - token: props.auth.token, - }), - }), - [props], - ); - useEffect(() => { - async function fetcher() { - setLoading(true); - if (location.state?.flag && flagMap[location.state.flag]) { - await flagMap[location.state.flag](); - } else { - await props.getActivities(props.t); - } - setActivityList(activities.all_activities); - setLoading(false); - } + setActivityList(activities.all_activities); + }, [activities]); - fetcher(); + const flagMap = { + staff: () => + props.getUnPublishedActivities({ + t: props.t, + token: props.auth.token, + }), + educator: () => + props.getMyActivities({ + t: props.t, + token: props.auth.token, + }), + }; + useEffect(async () => { + setLoading(true); + if (location.state?.flag && flagMap[location.state.flag]) { + await flagMap[location.state.flag](); + } else { + await props.getActivities(props.t); + } + setActivityList(activities.all_activities); + setLoading(false); }, [location]); if (loading) { @@ -110,6 +107,9 @@ const mapDispatchToProps = dispatch => { getActivities: args => { return dispatch(getActivities(args)); }, + getMyActivities: args => { + return dispatch(getMyActivities(args)); + }, getUnPublishedActivities: args => { return dispatch(getUnPublishedActivities(args)); }, diff --git a/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx b/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx index a544468b1..c2ab283cc 100644 --- a/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx +++ b/zubhub_frontend/zubhub/src/views/activity_details/ActivityDetailsV2.jsx @@ -11,7 +11,7 @@ import { Typography, makeStyles, } from '@material-ui/core'; -import { CloseOutlined, MoreVert } from '@material-ui/icons'; +import { CloseOutlined, ExpandMore, MoreVert } from '@material-ui/icons'; import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder'; import VisibilityIcon from '@material-ui/icons/Visibility'; import clsx from 'clsx'; @@ -32,7 +32,6 @@ import { getUrlQueryObject } from '../../utils.js'; import { activityDefailsStyles } from './ActivityDetails.styles'; import { useReactToPrint } from 'react-to-print'; import Html2Pdf from 'html2pdf.js'; -import { toast } from 'react-toastify'; const API = new ZubHubAPI(); const authenticatedUserActivitiesGrid = { xs: 12, sm: 6, md: 6 }; @@ -82,18 +81,6 @@ export default function ActivityDetailsV2(props) { props.history.push(`${props.location.pathname}/edit`); }; - const handleUpublishAndPublish = async () => { - const validData = Object.fromEntries(Object.entries(activity).filter(([key, value]) => value !== null)); - const newData = { ...validData, publish: !activity.publish }; - const response = await API.updateActivity(auth.token, props.match.params.id, newData); - - if (response.status === 200) { - const data = await response.json(); - toast.success(data.publish ? `Successfully published ${data.title}` : `You've unpublished ${data.title}`); - props.history.push(`/activities`); - } - }; - const toggleDialog = () => { setOpen(!open); props.history.replace(window.location.pathname); @@ -148,15 +135,7 @@ export default function ActivityDetailsV2(props) {
- {['staff', 'educator'].some(tag => props.auth.tags.includes(tag)) && ( - - )} +
@@ -281,18 +260,22 @@ export default function ActivityDetailsV2(props) { - {moreActivities - .filter(({ id }) => id !== props.match.params.id) - .map((activity, index) => ( - - - - ))} + {moreActivities.map((activity, index) => ( + + handleSetState(updateProjects(res, state, props, toast))} + {...props} + /> + + ))} {/* */} @@ -323,7 +306,7 @@ export default function ActivityDetailsV2(props) { ); } -const AnchorElemt = ({ activity, onEdit, onUpdate, onDelete, isLoading = false }) => { +const AnchorElemt = ({ onEdit, onDelete, isLoading = false }) => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); @@ -335,11 +318,6 @@ const AnchorElemt = ({ activity, onEdit, onUpdate, onDelete, isLoading = false } setAnchorEl(null); }; - const handleUpdate = () => { - onUpdate(); - handleClose(); - }; - const handledelete = () => { onDelete(); handleClose(); @@ -372,8 +350,6 @@ const AnchorElemt = ({ activity, onEdit, onUpdate, onDelete, isLoading = false } > Edit - {activity.publish ? 'Unpublish' : 'Publish'} - Delete diff --git a/zubhub_frontend/zubhub/src/views/create_activity/CreateEditActivity.jsx b/zubhub_frontend/zubhub/src/views/create_activity/CreateActivity.jsx similarity index 68% rename from zubhub_frontend/zubhub/src/views/create_activity/CreateEditActivity.jsx rename to zubhub_frontend/zubhub/src/views/create_activity/CreateActivity.jsx index 224df598f..e4e3cef29 100644 --- a/zubhub_frontend/zubhub/src/views/create_activity/CreateEditActivity.jsx +++ b/zubhub_frontend/zubhub/src/views/create_activity/CreateActivity.jsx @@ -12,23 +12,27 @@ import { makeStyles, useMediaQuery, } from '@material-ui/core'; -import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react'; -import { CustomButton, PreviewActivity } from '../../components'; +import React, { useEffect, useRef, useState } from 'react'; +import { CustomButton, Modal, PreviewActivity, TagsInput } from '../../components'; import StepWizard from 'react-step-wizard'; import { ArrowBackIosRounded, ArrowForwardIosRounded, + CloseOutlined, CloudDoneOutlined, DoneRounded, KeyboardBackspaceRounded, } from '@material-ui/icons'; +import { AiOutlineExclamationCircle } from 'react-icons/ai'; import { createActivityStyles } from './CreateActivity.styles'; import clsx from 'clsx'; +import { colors } from '../../assets/js/colors'; import styles from '../../assets/js/styles'; import { useDomElementHeight } from '../../hooks/useDomElementHeight.hook'; import { toast } from 'react-toastify'; import { useFormik } from 'formik'; import * as script from './script'; +import CreateActivityStep1 from './create_activity_step1'; import Step1 from './step1/Step1'; import Step2 from './step2/Step2'; import { useSelector } from 'react-redux'; @@ -38,14 +42,7 @@ const steps = ['Activity Basics', 'Activity Details']; let firstRender = true; -function CreateEditActivity(props) { - useEffect(() => { - if (!['staff', 'educator'].some(tag => props.auth.tags.includes(tag))) { - props.history.push('/activities'); - } - }, [props.auth.tags, props.history]); - - const editting = props?.editting; +export default function CreateActivity(props) { const { height } = useDomElementHeight('navbar-root'); const isSmallScreen = useMediaQuery(theme => theme.breakpoints.down('sm')); @@ -62,8 +59,8 @@ function CreateEditActivity(props) { const [preview, setPreview] = useState(false); const [isLoading, setIsLoading] = useState(false); - const isActive = useCallback(index => index + 1 === activeStep, [activeStep]); - const isCompleted = useCallback(index => completedSteps.includes(index + 1), [completedSteps]); + const isActive = index => index + 1 === activeStep; + const isCompleted = index => completedSteps.includes(index + 1); const handleSetState = obj => { if (obj) { @@ -105,40 +102,45 @@ function CreateEditActivity(props) { const formikStep2 = useFormik(script.step2Schema); const previous = () => go('prev'); + const next = async () => { + let error = await checkErrors(); - const submit = async () => { - const errors = await checkErrors(); - - if (Object.keys(errors).length > 0) { - Object.keys(errors).map(err => { - return toast.warn(err); - }); - return; - } + console.log(error); + if (Object.keys(error || {}).length > 0) return; setIsLoading(true); - script.submitForm({ - step1Values: formikStep1.values, - step2Values: formikStep2.values, - props: { ...props, auth }, - state, - handleSetState: handleSetState, - step: activeStep, - publish: true, - }); + script.submitForm( + { + step1Values: formikStep1.values, + step2Values: formikStep2.values, + props: { ...props, auth }, + state, + handleSetState: handleSetState, + step: activeStep, + }, + success => { + if (success) { + if (activeStep == 1) go('next'); + if (activeStep == 2) props.history.push(`/activities/${props.match.params.id}?success=true`); + setIsLoading(false); + } + }, + ); }; const checkErrors = () => { - return formikStep2.setTouched({ introduction: true, materials_used: true }, true); + if (activeStep === 1) { + return formikStep1.setTouched({ title: true }, true); + } + + if (activeStep === 2) { + return formikStep2.setTouched({ introduction: true }, true); + } }; - const go = async direction => { + const go = direction => { if (direction === 'next') { - const errors = await formikStep1.setTouched({ title: true, class_grade: true, category: true }, true); - - if (Object.keys(errors).length > 0) return; - if (activeStep !== 3) { wizardRef.current.nextStep(); let completedStepsTemp = [...new Set([...completedSteps, activeStep])]; @@ -156,37 +158,23 @@ function CreateEditActivity(props) { } }; - const renderSteps = useMemo( - () => - steps.map((label, index) => ( - - - {isCompleted(index) && !isActive(index) ? ( - - ) : ( - {index + 1} - )} - - - {label} - - - )), - [ - classes.activeLabel, - classes.activeStep, - classes.stepBall, - classes.stepLabel, - classes.stepperLine, - isActive, - isCompleted, - ], - ); + const renderSteps = steps.map((label, index) => ( + + + {isCompleted(index) && !isActive(index) ? ( + + ) : ( + {index + 1} + )} + + + {label} + + + )); return (
@@ -215,9 +203,7 @@ function CreateEditActivity(props) { - - {editting ? 'Edit Activity' : 'Create Activity'} - + Create Activity Tell us about your informative activity ! @@ -243,19 +229,18 @@ function CreateEditActivity(props) { Previous )} + go('next')} + onClick={next} loading={isLoading} style={{ marginLeft: 'auto' }} primaryButtonStyle endIcon={} > - {activeStep === 2 ? (editting ? 'Update' : 'Publish') : 'Next'} + {activeStep == 2 ? 'Publish' : 'Next'}
); } - -export default CreateEditActivity; diff --git a/zubhub_frontend/zubhub/src/views/create_activity/createActivityScripts.js b/zubhub_frontend/zubhub/src/views/create_activity/createActivityScripts.js index 7ad03da1d..b0d5d8237 100644 --- a/zubhub_frontend/zubhub/src/views/create_activity/createActivityScripts.js +++ b/zubhub_frontend/zubhub/src/views/create_activity/createActivityScripts.js @@ -82,7 +82,9 @@ const allEmptyObjects = arr => { const imageValidationSchema = Yup.mixed() .test('not_an_image', 'onlyImages', value => isImage(value)) - .test('image_size_too_large', 'imageSizeTooLarge', value => imageSizeTooLarge(value)); + .test('image_size_too_large', 'imageSizeTooLarge', value => + imageSizeTooLarge(value), + ); export const validationSchema = Yup.object().shape({ title: Yup.string().max(50, 'max').required('required'), @@ -129,7 +131,9 @@ export const validationSchema = Yup.object().shape({ activity_images: Yup.mixed() .test('not_an_image', 'onlyImages', value => isImage(value)) .test('too_many_images', 'tooManyImages', value => tooManyImages(value)) - .test('image_size_too_large', 'imageSizeTooLarge', value => imageSizeTooLarge(value)) + .test('image_size_too_large', 'imageSizeTooLarge', value => + imageSizeTooLarge(value), + ) .test({ message: 'required1', test: arr => { @@ -216,7 +220,11 @@ export const validationSchema = Yup.object().shape({ export const getErrors = (route, field, index, errors, touched) => { return route ? index < 0 - ? errors[route] && errors[route][field] && touched[route] && touched[route][field] && errors[route][field] + ? errors[route] && + errors[route][field] && + touched[route] && + touched[route][field] && + errors[route][field] : errors[route] && errors[route][index] && errors[route][index][field] && @@ -228,11 +236,20 @@ export const getErrors = (route, field, index, errors, touched) => { ? errors[field] && touched[field] && errors[field] : errors[field] && touched[field] && typeof errors[field] === 'string' ? errors[field] - : errors[field] && errors[field][index] && touched[field] && touched[field][index] && errors[field][index]; + : errors[field] && + errors[field][index] && + touched[field] && + touched[field][index] && + errors[field][index]; }; export const getMakingStepsRequiredError = (route, errors, touched) => { - return errors[route] && touched[route] && typeof errors[route] === 'string' && errors[route]; + return ( + errors[route] && + touched[route] && + typeof errors[route] === 'string' && + errors[route] + ); // if (route && errors[route] && errors[route][index] && touched[route]) { // return errors[route][index]; @@ -241,7 +258,11 @@ export const getMakingStepsRequiredError = (route, errors, touched) => { export const getStepError = (route, index, errors, touched) => { return ( - errors[route] && touched[route] && typeof errors[route] !== 'string' && errors[route][index] && errors[route][index] + errors[route] && + touched[route] && + typeof errors[route] !== 'string' && + errors[route][index] && + errors[route][index] ); }; @@ -251,13 +272,22 @@ export const getValue = (route, field, index, fieldType, values) => { ? values[field] && values[field][index] : values[field] : fieldType.array - ? values[route] && values[route][index] && values[route][index][field] && values[route][index][field] + ? values[route] && + values[route][index] && + values[route][index][field] && + values[route][index][field] : values[route] && values[route][field]; }; ///////////////////////////////// deserialize activity object to be displayed for update in form fields ////////////////////// -const activityFieldMap = ['facilitation_tips', 'learning_goals', 'materials_used', 'motivation', 'title']; +const activityFieldMap = [ + 'facilitation_tips', + 'learning_goals', + 'materials_used', + 'motivation', + 'title', +]; const objectsArray = ['making_steps', 'inspiring_examples']; @@ -342,10 +372,18 @@ const isEmptyObject = obj => { return isEmpty; }; -export const initUpload = async (e, state, props, handleSetState, history, formikProps, setReadyForSubmit) => { +export const initUpload = async ( + e, + state, + props, + handleSetState, + history, + formikProps, + setReadyForSubmit, +) => { e.preventDefault(); handleSetState(state => { - return { ...state, submitting: true }; + return { ...state, ['submitting']: true }; }); if (!props.auth.token) { props.history.push('/login'); @@ -356,7 +394,7 @@ export const initUpload = async (e, state, props, handleSetState, history, formi handleSetState, formikProps.formikValues, props.getSignature, - props.t, + props.t ); let values = { ...formikProps.formikValues }; @@ -401,8 +439,10 @@ export const initUpload = async (e, state, props, handleSetState, history, formi } }); } catch (error) { - console.error('error ', error); - toast.warning(`${props.t(`activities.errors.dialog.${error.message}`)} ${error.file}`); + console.log('error ', error); + toast.warning( + `${props.t(`activities.errors.dialog.${error.message}`)} ${error.file}`, + ); // if (error.error.response.status >= 500) { // toast.warning( // `${props.t(`activities.errors.dialog.mediaServerError`)}`, @@ -437,7 +477,9 @@ export const initUpload = async (e, state, props, handleSetState, history, formi fieldValues.push(value); } }); - fieldValues.length === 0 ? delete values[field] : (values[field] = fieldValues); + fieldValues.length === 0 + ? delete values[field] + : (values[field] = fieldValues); } }); @@ -505,11 +547,15 @@ export const initUpload = async (e, state, props, handleSetState, history, formi response.then(res => { props.setActivity(res); }); - toast.success(props.t('activityDetails.activity.edit.dialog.success')); + toast.success( + props.t('activityDetails.activity.edit.dialog.success'), + ); return props.history.push(`/activities/${values['id']}`); } else { if (res.status === 403 && res.statusText === 'Forbidden') { - toast.warning(props.t('activityDetails.activity.delete.dialog.forbidden')); + toast.warning( + props.t('activityDetails.activity.delete.dialog.forbidden'), + ); } else { toast.warning(props.t('activities.errors.dialog.serverError')); } @@ -526,12 +572,16 @@ export const initUpload = async (e, state, props, handleSetState, history, formi const response = res.json(); response.then(res => { props.setActivity(res); - toast.success(props.t('activityDetails.activity.create.dialog.success')); + toast.success( + props.t('activityDetails.activity.create.dialog.success'), + ); return props.history.push(`/activities/${res.id}`); }); } else { if (res.status === 403 && res.statusText === 'Forbidden') { - toast.warning(props.t('activityDetails.activity.create.dialog.forbidden')); + toast.warning( + props.t('activityDetails.activity.create.dialog.forbidden'), + ); } else { // if (res.status === 404 || res.status === 400) { // res.json().then(res => { diff --git a/zubhub_frontend/zubhub/src/views/create_activity/script.js b/zubhub_frontend/zubhub/src/views/create_activity/script.js index 0c202d2f0..4e5e644a7 100644 --- a/zubhub_frontend/zubhub/src/views/create_activity/script.js +++ b/zubhub_frontend/zubhub/src/views/create_activity/script.js @@ -10,723 +10,787 @@ const idPrefix = 'activitystep'; const API = new ZubhubAPI(); export const class_grades = [ - { name: 'Grade 1-3', id: '1-3' }, - { name: 'Grade 4-6', id: '4-6' }, - { name: 'Grade 7-9', id: '7-9' }, - { name: 'Grade 10-12', id: '10-12' }, + { name: 'Grade 1-3', id: '1-3' }, + { name: 'Grade 4-6', id: '4-6' }, + { name: 'Grade 7-9', id: '7-9' }, + { name: 'Grade 10-12', id: '10-12' }, ]; export const vars = { - image_field_touched: false, - video_field_touched: false, - upload_in_progress: false, - timer: { id: null }, - default_state: { - loading: true, - error: null, - desc_input_is_focused: false, - video_upload_dialog_open: false, - select_video_file: false, - materials_used: [], - categories: [], - tag_suggestion: [], - tag_suggestion_open: false, - publish_types: [], - publish_visible_to_suggestion: [], - publish_visible_to_suggestion_open: false, - media_upload: { - upload_dialog: false, - images_to_upload: [], - videos_to_upload: [], - upload_info: {}, - upload_percent: 0, - uploaded_images_url: [], - uploaded_videos_url: [], + image_field_touched: false, + video_field_touched: false, + upload_in_progress: false, + timer: { id: null }, + default_state: { + loading: true, + error: null, + desc_input_is_focused: false, + video_upload_dialog_open: false, + select_video_file: false, + materials_used: [], + categories: [], + tag_suggestion: [], + tag_suggestion_open: false, + publish_types: [], + publish_visible_to_suggestion: [], + publish_visible_to_suggestion_open: false, + media_upload: { + upload_dialog: false, + images_to_upload: [], + videos_to_upload: [], + upload_info: {}, + upload_percent: 0, + uploaded_images_url: [], + uploaded_videos_url: [], + }, }, - }, - quill: { - modules: { - toolbar: [ - ['bold', 'italic', 'underline', 'strike'], - [{ header: [1, 2, 3, 4, 5, 6] }], - [{ list: 'ordered' }, { list: 'bullet' }], - [{ indent: '-1' }, { indent: '+1' }], - ['code-block'], - ], + quill: { + modules: { + toolbar: [ + ['bold', 'italic', 'underline', 'strike'], + [{ header: [1, 2, 3, 4, 5, 6] }], + [{ list: 'ordered' }, { list: 'bullet' }], + [{ indent: '-1' }, { indent: '+1' }], + ['code-block'], + ], + }, + formats: [ + 'header', + 'bold', + 'italic', + 'underline', + 'strike', + 'blockquote', + 'script', + 'list', + 'bullet', + 'indent', + 'link', + 'image', + 'color', + 'code-block', + ], }, - formats: [ - 'header', - 'bold', - 'italic', - 'underline', - 'strike', - 'blockquote', - 'script', - 'list', - 'bullet', - 'indent', - 'link', - 'image', - 'color', - 'code-block', - ], - }, }; + + + + + export const handleMaterialsUsedFieldBlur = props => { - props.setStatus({ ...props.status, materials_used: '' }); - props.setFieldTouched('materials_used', true); + props.setStatus({ ...props.status, materials_used: '' }); + props.setFieldTouched('materials_used', true); }; export const removeMetaData = (images, state, handleSetState) => { - const newWorker = worker(); - newWorker.removeMetaData(images); - newWorker.addEventListener('message', e => { - Compress(e.data, state, handleSetState); - }); + const newWorker = worker(); + newWorker.removeMetaData(images); + newWorker.addEventListener('message', e => { + Compress(e.data, state, handleSetState); + }); }; + export const initUpload = (state, props, handleSetState) => { - if (!props.auth.token) return props.history.push('/login'); + if (!props.auth.token) return props.history.push('/login'); + + if (!(props.values.images.length !== 0 || props.values.video.length !== 0)) { + vars.upload_in_progress = true; + return uploadProject(state, props, handleSetState); + } - if (!(props.values.images.length !== 0 || props.values.video.length !== 0)) { vars.upload_in_progress = true; - return uploadProject(state, props, handleSetState); - } + state.media_upload.upload_dialog = true; + handleSetState({ + success: false, + default_state: { + loading: true, + }, + media_upload: { + ...state.media_upload, + upload_dialog: true, + upload_percent: 0, + }, + }); - vars.upload_in_progress = true; - state.media_upload.upload_dialog = true; - handleSetState({ - success: false, - default_state: { - loading: true, - }, - media_upload: { - ...state.media_upload, - upload_dialog: true, - upload_percent: 0, - }, - }); - - const promises = []; - - // upload images - for (let index = 0; index < props.values.images.filter(img => !img.link).length; index++) { - promises.push(uploadImage(props.values.images[index], state, props, handleSetState)); - } - - // upload videos - for (let index = 0; index < props.values.video.length; index++) { - promises.push(uploadVideo(props.values.video[index], state, props, handleSetState)); - } - - // wait for all image and video promises to resolve before continuing - Promise.all(promises) - .then(all => { - const uploaded_images_url = state.media_upload.uploaded_images_url; - const uploaded_videos_url = state.media_upload.uploaded_videos_url; - - all.forEach(each => { - if (each.public_id) { - uploaded_images_url.push(each); - } else if (each.secure_url) { - uploaded_videos_url[0] = each.secure_url; - } - }); + const promises = []; - state = JSON.parse(JSON.stringify(state)); - state.media_upload.uploaded_images_url = uploaded_images_url; - state.media_upload.uploaded_videos_url = uploaded_videos_url; + // upload images + for (let index = 0; index < props.values.images.filter(img => !img.link).length; index++) { + promises.push(uploadImage(props.values.images[index], state, props, handleSetState)); + } - uploadProject(state, props, handleSetState); - }) - .catch(error => { - // settimeout is used to delay closing the upload_dialog until - // state have reflected all prior attempts to set state. - // This is to ensure nothing overwrites the dialog closing. - // A better approach would be to refactor the app and use - // redux for most complex state interactions. - setTimeout( - () => - handleSetState({ - media_upload: { ...state.media_upload, upload_dialog: false }, - }), - 2000, - ); - - if (error) toast.warning(error); - }); + // upload videos + for (let index = 0; index < props.values.video.length; index++) { + promises.push(uploadVideo(props.values.video[index], state, props, handleSetState)); + } + + // wait for all image and video promises to resolve before continuing + Promise.all(promises) + .then(all => { + const uploaded_images_url = state.media_upload.uploaded_images_url; + const uploaded_videos_url = state.media_upload.uploaded_videos_url; + + all.forEach(each => { + if (each.public_id) { + uploaded_images_url.push(each); + } else if (each.secure_url) { + uploaded_videos_url[0] = each.secure_url; + } + }); + + state = JSON.parse(JSON.stringify(state)); + state.media_upload.uploaded_images_url = uploaded_images_url; + state.media_upload.uploaded_videos_url = uploaded_videos_url; + + uploadProject(state, props, handleSetState); + }) + .catch(error => { + // settimeout is used to delay closing the upload_dialog until + // state have reflected all prior attempts to set state. + // This is to ensure nothing overwrites the dialog closing. + // A better approach would be to refactor the app and use + // redux for most complex state interactions. + setTimeout( + () => + handleSetState({ + media_upload: { ...state.media_upload, upload_dialog: false }, + }), + 2000, + ); + + if (error) toast.warning(error); + }); }; + export const uploadProject = async (state, props, handleSetState) => { - const materials_used = props.values['materials_used'].filter(value => (value ? true : false)).join(','); - - const tags = props.values['tags'] - ? props.values['tags'].map(tag => (typeof tag === 'string' ? { name: tag } : tag)) - : []; - - const create_or_update = props.match.params.id ? props.updateProject : props.createProject; - create_or_update({ - ...props.values, - tags, - materials_used, - id: props.match.params.id, - token: props.auth.token, - activity: props.location.state?.activity_id, - images: state.media_upload.uploaded_images_url || '', - video: state.media_upload.uploaded_videos_url[0] || props.values.video_link, - category: props.values.category.filter(cat => cat.name).map(cat => cat?.name), - t: props.t, - publish: { type: props.step < 3 ? 1 : 4, visible_to: [] }, - }) - .then(id => { - handleSetState({ ...state, default_state: { loading: false }, success: true, id: `${id}` }); + const materials_used = props.values['materials_used'] + .filter(value => (value ? true : false)) + .join(','); + + const tags = props.values['tags'] ? props.values['tags'].map(tag => typeof tag === 'string' ? { name: tag } : tag) : []; + + const create_or_update = props.match.params.id ? props.updateProject : props.createProject; + create_or_update({ + ...props.values, + tags, + materials_used, + id: props.match.params.id, + token: props.auth.token, + activity: props.location.state?.activity_id, + images: state.media_upload.uploaded_images_url || '', + video: state.media_upload.uploaded_videos_url[0] || props.values.video_link, + category: props.values.category.filter(cat => cat.name).map(cat => cat?.name), + t: props.t, + publish: { type: props.step < 3 ? 1 : 4, visible_to: [] } }) - .catch(error => { - console.log(error, 'error'); - handleSetState({ - media_upload: { - ...state.media_upload, - upload_dialog: false, - default_state: { loading: false }, - }, - }); - const messages = JSON.parse(error.message); - if (typeof messages === 'object') { - const server_errors = {}; - Object.keys(messages).forEach(key => { - if (key === 'non_field_errors') { - server_errors['non_field_errors'] = messages[key][0]; - } else { - server_errors[key] = messages[key][0]; - } + .then((id) => { + handleSetState({ ...state, default_state: { loading: false }, success: true, id: `${id}` }) + }) + .catch(error => { + console.log(error, 'error'); + handleSetState({ + media_upload: { + ...state.media_upload, + upload_dialog: false, + default_state: { loading: false } + }, + }); + const messages = JSON.parse(error.message); + if (typeof messages === 'object') { + const server_errors = {}; + Object.keys(messages).forEach(key => { + if (key === 'non_field_errors') { + server_errors['non_field_errors'] = messages[key][0]; + } else { + server_errors[key] = messages[key][0]; + } + }); + props.setStatus({ ...server_errors }); + } else { + props.setStatus({ + non_field_errors: props.t('createProject.errors.unexpected'), + }); + } + }) + .finally(() => { + vars.upload_in_progress = false; // flag to prevent attempting to upload a project when an upload is already in progress }); - props.setStatus({ ...server_errors }); - } else { - props.setStatus({ - non_field_errors: props.t('createProject.errors.unexpected'), - }); - } - }) - .finally(() => { - vars.upload_in_progress = false; // flag to prevent attempting to upload a project when an upload is already in progress - }); }; + export const uploadVideo = (video, state, props, handleSetState) => { - if ( - typeof video === 'string' && - video.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g) - ) { - return new Promise(r => r({ secure_url: video })); - } else { - const args = { - t: props.t, - token: props.auth.token, - }; + if ( + typeof video === 'string' && + video.match( + /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g, + ) + ) { + return new Promise(r => r({ secure_url: video })); + } else { + const args = { + t: props.t, + token: props.auth.token, + }; - return API.shouldUploadToLocal(args).then(res => { - if (res && res.local === true) { - return uploadVideoToLocal(video, state, props, handleSetState); - } else if (res && res.local === false) { - return uploadVideoToCloudinary(video, state, props, handleSetState); - } - }); - } + return API.shouldUploadToLocal(args).then(res => { + if (res && res.local === true) { + return uploadVideoToLocal(video, state, props, handleSetState); + } else if (res && res.local === false) { + return uploadVideoToCloudinary(video, state, props, handleSetState); + } + }); + } }; + export const uploadVideoToLocal = (video, state, props, handleSetState) => { - let url = - process.env.REACT_APP_NODE_ENV === 'production' - ? process.env.REACT_APP_BACKEND_PRODUCTION_URL + '/api/' - : process.env.REACT_APP_BACKEND_DEVELOPMENT_URL + '/api/'; - url = url + 'upload-file-to-local/'; - - let key = nanoid(); - key = key.slice(0, Math.floor(key.length / 3)); - key = `videos/${slugify(props.auth.username)}-${slugify(video.name)}-${key}`; - - const formData = new FormData(); - formData.append('file', video); - formData.append('key', key); - - return new Promise((resolve, reject) => { - const um = new UploadMedia('video', url, formData, state, props, handleSetState, resolve, reject); - um.upload(); - }); + let url = + process.env.REACT_APP_NODE_ENV === 'production' + ? process.env.REACT_APP_BACKEND_PRODUCTION_URL + '/api/' + : process.env.REACT_APP_BACKEND_DEVELOPMENT_URL + '/api/'; + url = url + 'upload-file-to-local/'; + + let key = nanoid(); + key = key.slice(0, Math.floor(key.length / 3)); + key = `videos/${slugify(props.auth.username)}-${slugify(video.name)}-${key}`; + + const formData = new FormData(); + formData.append('file', video); + formData.append('key', key); + + return new Promise((resolve, reject) => { + const um = new UploadMedia( + 'video', + url, + formData, + state, + props, + handleSetState, + resolve, + reject, + ); + um.upload(); + }); }; -export const uploadVideoToCloudinary = (video, state, props, handleSetState) => { - const url = process.env.REACT_APP_VIDEO_UPLOAD_URL; - - const upload_preset = - process.env.REACT_APP_NODE_ENV === 'production' - ? process.env.REACT_APP_VIDEO_UPLOAD_PRESET_NAME - : process.env.REACT_APP_DEV_VIDEO_UPLOAD_PRESET_NAME; - - const params = { - upload_preset, - username: props.auth.username, - filename: video.name, - t: props.t, - token: props.auth.token, - }; - - return props.getSignature(params).then(sig_res => { - if (typeof sig_res === 'object') { - const formData = new FormData(); - formData.append('file', video); - formData.append('public_id', sig_res.public_id); - formData.append('upload_preset', upload_preset); - formData.append('api_key', sig_res.api_key); - formData.append('timestamp', sig_res.timestamp); - formData.append('signature', sig_res.signature); - - return new Promise((resolve, reject) => { - const um = new UploadMedia('video', url, formData, state, props, handleSetState, resolve, reject); - um.upload(); - }); - } else { - return Promise.reject(''); - } - }); + +export const uploadVideoToCloudinary = ( + video, + state, + props, + handleSetState, +) => { + const url = process.env.REACT_APP_VIDEO_UPLOAD_URL; + + const upload_preset = + process.env.REACT_APP_NODE_ENV === 'production' + ? process.env.REACT_APP_VIDEO_UPLOAD_PRESET_NAME + : process.env.REACT_APP_DEV_VIDEO_UPLOAD_PRESET_NAME; + + const params = { + upload_preset, + username: props.auth.username, + filename: video.name, + t: props.t, + token: props.auth.token, + }; + + return props.getSignature(params).then(sig_res => { + if (typeof sig_res === 'object') { + const formData = new FormData(); + formData.append('file', video); + formData.append('public_id', sig_res.public_id); + formData.append('upload_preset', upload_preset); + formData.append('api_key', sig_res.api_key); + formData.append('timestamp', sig_res.timestamp); + formData.append('signature', sig_res.signature); + + return new Promise((resolve, reject) => { + const um = new UploadMedia( + 'video', + url, + formData, + state, + props, + handleSetState, + resolve, + reject, + ); + um.upload(); + }); + } else { + return Promise.reject(''); + } + }); }; + export const uploadImage = (image, state, props, handleSetState) => { - const args = { - t: props.t, - token: props.auth.token, - }; - - return API.shouldUploadToLocal(args).then(res => { - if (res && res.local === true) { - return uploadImageToLocal(image, state, props, handleSetState); - } else if (res && res.local === false) { - return uploadImageToDO(image, state, props, handleSetState); - } - }); -}; + const args = { + t: props.t, + token: props.auth.token, + }; -export const uploadImageToLocal = (image, state, props, handleSetState) => { - let url = - process.env.REACT_APP_NODE_ENV === 'production' - ? process.env.REACT_APP_BACKEND_PRODUCTION_URL + '/api/' - : process.env.REACT_APP_BACKEND_DEVELOPMENT_URL + '/api/'; - url = url + 'upload-file-to-local/'; - - const formData = new FormData(); - formData.append('file', image); - formData.append('key', `project_images/${nanoid()}`); - return new Promise((resolve, reject) => { - const um = new UploadMedia('image', url, formData, state, props, handleSetState, resolve, reject); - um.upload(); - }); + return API.shouldUploadToLocal(args).then(res => { + if (res && res.local === true) { + return uploadImageToLocal(image, state, props, handleSetState); + } else if (res && res.local === false) { + return uploadImageToDO(image, state, props, handleSetState); + } + }); }; -export const uploadImageToDO = (image, state, props, handleSetState) => { - return new Promise((resolve, reject) => { - const params = { - Bucket: `${doConfig.bucketName}`, - Key: `${doConfig.project_images}/${nanoid()}`, - Body: image, - ContentType: image.type, - ACL: 'public-read', - }; - DO.upload(params, err => { - reject(err.message); - }) - .on('httpUploadProgress', e => { - const progress = Math.round((e.loaded * 100.0) / e.total); - const { media_upload } = state; - const upload_info = JSON.parse(JSON.stringify(media_upload.upload_info)); - upload_info[image.name] = progress; +export const uploadImageToLocal = (image, state, props, handleSetState) => { + let url = + process.env.REACT_APP_NODE_ENV === 'production' + ? process.env.REACT_APP_BACKEND_PRODUCTION_URL + '/api/' + : process.env.REACT_APP_BACKEND_DEVELOPMENT_URL + '/api/'; + url = url + 'upload-file-to-local/'; + + const formData = new FormData(); + formData.append('file', image); + formData.append('key', `project_images/${nanoid()}`); + return new Promise((resolve, reject) => { + const um = new UploadMedia( + 'image', + url, + formData, + state, + props, + handleSetState, + resolve, + reject, + ); + um.upload(); + }); +}; - let total = 0; - Object.keys(upload_info).forEach(each => { - total = total + upload_info[each]; - }); - total = total / Object.keys(upload_info).length; +export const uploadImageToDO = (image, state, props, handleSetState) => { + return new Promise((resolve, reject) => { + const params = { + Bucket: `${doConfig.bucketName}`, + Key: `${doConfig.project_images}/${nanoid()}`, + Body: image, + ContentType: image.type, + ACL: 'public-read', + }; - handleSetState({ - default_state: { loading: false }, - media_upload: { - ...media_upload, - upload_info, - upload_percent: total, - }, - }); - }) - .send((err, data) => { - if (err) { - if (err.message.startsWith('Unexpected')) { - const error = props.t('createProject.errors.unexpected'); - reject(error); - } else { + DO.upload(params, err => { reject(err.message); - } - } else { - const secure_url = data.Location; - const public_id = data.Key; - resolve({ image_url: secure_url, public_id }); - } - }); - }); + }) + .on('httpUploadProgress', e => { + const progress = Math.round((e.loaded * 100.0) / e.total); + const { media_upload } = state; + const upload_info = JSON.parse( + JSON.stringify(media_upload.upload_info), + ); + upload_info[image.name] = progress; + + let total = 0; + Object.keys(upload_info).forEach(each => { + total = total + upload_info[each]; + }); + + total = total / Object.keys(upload_info).length; + + handleSetState({ + default_state: { loading: false }, + media_upload: { + ...media_upload, + upload_info, + upload_percent: total, + }, + }); + }) + .send((err, data) => { + if (err) { + if (err.message.startsWith('Unexpected')) { + const error = props.t('createProject.errors.unexpected'); + reject(error); + } else { + reject(err.message); + } + } else { + const secure_url = data.Location; + const public_id = data.Key; + resolve({ image_url: secure_url, public_id }); + } + }); + }); }; + export const getActivity = (props, state) => { - return API.getActivity({ - id: props.match.params.id, - token: props.auth.token, - }).then(obj => { - if (obj) { - const { formikStep1, formikStep2 } = props; - const step1Data = {}; - const step2Data = {}; - - if (obj.title) { - step1Data.title = obj.title; - } - - if (obj.category) { - step1Data.category = obj.category.map(cat => ({ name: cat, id: cat })); - } - - if (obj.class_grade) { - step1Data.class_grade = class_grades.find(item => item.name === obj.class_grade); - } - - if (obj.introduction) { - step2Data.introduction = obj.introduction; - } - - if (obj.images) { - step2Data.images = obj.images?.map(img => ({ ...img.image, name: img.image.file_url, link: true })); - } - - if (obj.materials_used) { - step2Data.materials_used = obj.materials_used; - } - - if (obj.materials_used_image) { - step2Data.materials_used_image = [ - { ...obj.materials_used_image, link: true, name: obj.materials_used_image.file_url }, - ]; - } - - if (obj.making_steps) { - const steps = obj.making_steps.map(step => { - step.images = step.image.map(img => ({ ...img, name: img.file_url, link: true })); - delete step.image; - return { ...step, id: uniqueId(idPrefix) }; - }); - step2Data.making_steps = steps; - } - formikStep1.setValues(step1Data, true); - formikStep2.setValues(step2Data, true); - } - return obj; - }); + return API.getActivity({ + id: props.match.params.id, + token: props.auth.token, + }) + .then(obj => { + if (obj) { + const { formikStep1, formikStep2 } = props + const step1Data = {} + const step2Data = {} + + + if (obj.title) { + step1Data.title = obj.title + } + + if (obj.category) { + step1Data.category = obj.category.map(cat => ({ name: cat, id: cat })) + } + + if (obj.class_grade) { + step1Data.class_grade = class_grades.find(item => item.name === obj.class_grade) + } + + if (obj.introduction) { + step2Data.introduction = obj.introduction + } + + if (obj.images) { + step2Data.images = obj.images?.map(img => ({ ...img.image, name: img.image.file_url, link: true })) + } + + if (obj.materials_used) { + step2Data.materials_used = obj.materials_used + } + + if (obj.materials_used_image) { + step2Data.materials_used_image = [{ ...obj.materials_used_image, link: true, name: obj.materials_used_image.file_url }] + } + + if (obj.making_steps) { + const steps = obj.making_steps.map(step => { + step.images = step.image.map(img => ({ ...img, name: img.file_url, link: true })) + delete step.image + return { ...step, id: uniqueId(idPrefix) } + }) + step2Data.making_steps = steps; + } + + formikStep1.setValues(step1Data, true) + formikStep2.setValues(step2Data, true) + } + return obj + }); }; + export const step1Validation = Yup.object().shape({ - title: Yup.string().max(100, 'max').required('required'), - class_grade: Yup.object().shape({ name: Yup.string() }).required('Select a grade'), - category: Yup.array().of(Yup.object().shape({ - name: Yup.string().required() - })).required('A category is required') + title: Yup.string().max(100, 'max').required('required'), + class_grade: Yup.object().shape({ name: Yup.string() }).nullable(), + category: Yup.array() + .of( + Yup.object().shape({ + name: Yup.string().required('Name is required') + }) + ), }); export const step1Schema = { - initialValues: { - title: undefined, - class_grade: null, - category: [], - }, - validationSchema: step1Validation, -}; + initialValues: { + title: undefined, + class_grade: null, + category: [], + }, + validationSchema: step1Validation, +} export const step2Validation = Yup.object().shape({ - introduction: Yup.string().max(1000, 'max').required('Add an introduction'), - materials_used: Yup.string().matches(/
  1. (?:\s*
    \s*|[a-zA-Z0-9]+)\s*<\/li><\/ol>/, { - message: 'Materials used are required', - excludeEmptyString: true, - }), - images: Yup.array().required('Add the activity image'), - making_steps: Yup.array().of( - Yup.object().shape({ - title: Yup.string(), - description: Yup.string().required(), - step_order: Yup.number(), - images: Yup.array(), - materials_used_image: Yup.array(), - }), - ).required('Atleast one step is required'), + introduction: Yup.string().max(1000, 'max').required('required'), + materials_used: Yup.string(), + images: Yup.array(), + making_steps: Yup.array().of(Yup.object().shape({ + title: Yup.string(), + description: Yup.string().required(), + step_order: Yup.number(), + images: Yup.array(), + materials_used_image: Yup.array() + })) }); export const step2Schema = { - initialValues: { - introduction: undefined, - images: [], - materials_used: '
    ', - making_steps: [], - materials_used_image: [], - }, - validationSchema: step2Validation, -}; + initialValues: { + introduction: undefined, + images: [], + materials_used: '
    ', + making_steps: [], + materials_used_image: [] + }, + validationSchema: step2Validation, +} export class UploadMedia { - constructor(type, url, formData, state, props, handleSetState, resolve, reject) { - this.xhr = new XMLHttpRequest(); - this.type = type; - this.url = url; - this.formData = formData; - this.state = state; - this.props = props; - this.handleSetState = handleSetState; - this.resolve = resolve; - this.reject = reject; - this.xhr.upload.onload = this.uploadOnLoad; - this.xhr.onreadystatechange = this.onReadyStateChange; - this.xhr.upload.onerror = this.uploadOnerror; - this.xhr.upload.onprogress = this.uploadOnprogress; - } - - uploadOnLoad = () => { - if (this.xhr.status !== 200 && this.xhr.readyState === 4) { - const error = this.props.t('createProject.errors.unexpected'); - this.reject(error); - } - }; - - onReadyStateChange = () => { - if (this.xhr.status === 200 && this.xhr.readyState === 4 && this.type === 'video') { - const data = JSON.parse(this.xhr.response); - const secure_url = data.secure_url; - this.resolve({ secure_url }); - } else if (this.xhr.status === 200 && this.xhr.readyState === 4 && this.type === 'image') { - const data = JSON.parse(this.xhr.response); - const secure_url = data.Location; - const public_id = data.Key; - - this.resolve({ image_url: secure_url, public_id }); + constructor( + type, + url, + formData, + state, + props, + handleSetState, + resolve, + reject, + ) { + this.xhr = new XMLHttpRequest(); + this.type = type; + this.url = url; + this.formData = formData; + this.state = state; + this.props = props; + this.handleSetState = handleSetState; + this.resolve = resolve; + this.reject = reject; + this.xhr.upload.onload = this.uploadOnLoad; + this.xhr.onreadystatechange = this.onReadyStateChange; + this.xhr.upload.onerror = this.uploadOnerror; + this.xhr.upload.onprogress = this.uploadOnprogress; } - }; - uploadOnerror = _ => { - const error = this.props.t('createProject.errors.unexpected'); + uploadOnLoad = () => { + if (this.xhr.status !== 200 && this.xhr.readyState === 4) { + const error = this.props.t('createProject.errors.unexpected'); + this.reject(error); + } + }; - this.reject(error); - }; + onReadyStateChange = () => { + if ( + this.xhr.status === 200 && + this.xhr.readyState === 4 && + this.type === 'video' + ) { + const data = JSON.parse(this.xhr.response); + const secure_url = data.secure_url; + this.resolve({ secure_url }); + } else if ( + this.xhr.status === 200 && + this.xhr.readyState === 4 && + this.type === 'image' + ) { + const data = JSON.parse(this.xhr.response); + const secure_url = data.Location; + const public_id = data.Key; + + this.resolve({ image_url: secure_url, public_id }); + } + }; - uploadOnprogress = e => { - const progress = Math.round((e.loaded * 100.0) / e.total); - const { media_upload } = this.state; + uploadOnerror = _ => { + const error = this.props.t('createProject.errors.unexpected'); - const upload_info = JSON.parse(JSON.stringify(media_upload.upload_info)); - upload_info[this.formData.get('file').name] = progress; + this.reject(error); + }; - let total = 0; - Object.keys(upload_info).forEach(each => { - total = total + upload_info[each]; - }); + uploadOnprogress = e => { + const progress = Math.round((e.loaded * 100.0) / e.total); + const { media_upload } = this.state; - total = total / Object.keys(upload_info).length; + const upload_info = JSON.parse(JSON.stringify(media_upload.upload_info)); + upload_info[this.formData.get('file').name] = progress; - this.handleSetState({ - media_upload: { - ...media_upload, - upload_info, - upload_percent: total, - }, - }); - }; - - upload = () => { - this.xhr.open('POST', this.url); - if (!this.url.startsWith(process.env.REACT_APP_VIDEO_UPLOAD_URL)) { - this.xhr.xsrfCookieName = 'csrftoken'; - this.xhr.xsrfHeaderName = 'X-CSRFToken'; - this.xhr.setRequestHeader('Authorization', `Token ${this.props.auth.token}`); - this.xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - } + let total = 0; + Object.keys(upload_info).forEach(each => { + total = total + upload_info[each]; + }); + + total = total / Object.keys(upload_info).length; + + this.handleSetState({ + media_upload: { + ...media_upload, + upload_info, + upload_percent: total, + }, + }); + }; + + upload = () => { + this.xhr.open('POST', this.url); + if (!this.url.startsWith(process.env.REACT_APP_VIDEO_UPLOAD_URL)) { + this.xhr.xsrfCookieName = 'csrftoken'; + this.xhr.xsrfHeaderName = 'X-CSRFToken'; + this.xhr.setRequestHeader( + 'Authorization', + `Token ${this.props.auth.token}`, + ); + this.xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + } - this.xhr.send(this.formData); - }; + this.xhr.send(this.formData); + }; } -export const submitForm = async ({ step1Values, step2Values, props, state, handleSetState, step, publish }) => { - let fileUploadResponse; - const uploadedImages = { images: [], materials_used_image: [], making_steps: {} }; - const imagesToUpload = { images: [], materials_used_image: [], making_steps: {} }; - - if (step === 2) { - // Add all images present in th form in the imagesToUpload object for upload. - - if (step2Values) { - step2Values.images.forEach(img => { - if (img.link) uploadedImages.images = [...uploadedImages.images, img]; - else imagesToUpload.images = [...imagesToUpload.images, img]; - }); - - step2Values.materials_used_image?.forEach(img => { - if (img.link) uploadedImages.materials_used_image = [...uploadedImages.materials_used_image, img]; - else imagesToUpload.materials_used_image = [...imagesToUpload.materials_used_image, img]; - }); - - step2Values.making_steps?.forEach((step, index) => { - uploadedImages.making_steps[`${index}`] = step.images.filter(img => img.link); - imagesToUpload.making_steps[`${index}`] = step.images.filter(img => !img.link); - }); - } - // Upload all files present in the form and get their remote data. - fileUploadResponse = await formFilesUpload(imagesToUpload, props, state, handleSetState); +export const submitForm = async ({ step1Values, step2Values, props, state, handleSetState, step }, callback) => { - if (fileUploadResponse.error) { - alert('Failed to upload Files; Please try again'); - return; - } - } - - const formDataStep1 = { - category: step1Values.category.map(item => item.name), - class_grade: step1Values.class_grade.name, - title: step1Values.title, - }; - - // Add - const formData = { - ...formDataStep1, - ...step2Values, - publish, - }; - - if (step === 2) { - let makinStepsImages = fileUploadResponse.filter(item => item.name.startsWith('making_steps')); - let images = fileUploadResponse.filter(item => item.name === 'images'); - let materials_used_image = fileUploadResponse.find(item => item.name === 'materials_used_image'); - - images = images[0]?.files.map(item => ({ image: replaceImageUrlWithFileUrl(item) })); - formData['images'] = [ - ...(images || []), - ...uploadedImages.images.map(item => ({ image: replaceImageUrlWithFileUrl(item) })), - ]; - - if (materials_used_image) { - formData['materials_used_image'] = { - file_url: materials_used_image.files[0].image_url, - public_id: materials_used_image.files[0].public_id, - }; + let fileUploadResponse; + const uploadedImages = { images: [], materials_used_image: [], making_steps: {} }; + const imagesToUpload = { images: [], materials_used_image: [], making_steps: {} }; + + if (step === 2) { + // Add all images present in th form in the imagesToUpload object for upload. + + if (step2Values) { + step2Values.images.forEach(img => { + if (img.link) uploadedImages.images = [...uploadedImages.images, img] + else imagesToUpload.images = [...imagesToUpload.images, img] + }) + + step2Values.materials_used_image?.forEach(img => { + if (img.link) uploadedImages.materials_used_image = [...uploadedImages.materials_used_image, img] + else imagesToUpload.materials_used_image = [...imagesToUpload.materials_used_image, img] + }) + + step2Values.making_steps?.forEach((step, index) => { + uploadedImages.making_steps[`${index}`] = step.images.filter(img => img.link) + imagesToUpload.making_steps[`${index}`] = step.images.filter(img => !img.link); + }) + } + + // Upload all files present in the form and get their remote data. + fileUploadResponse = await formFilesUpload(imagesToUpload, props, state, handleSetState); + + if (fileUploadResponse.error) { + alert('Failed to upload Files; Please try again'); + return; + } } - if (!materials_used_image && uploadedImages.materials_used_image[0]) { - materials_used_image = uploadedImages.materials_used_image[0]; - formData['materials_used_image'] = { - file_url: materials_used_image.file_url, - public_id: materials_used_image.public_id, - }; + const formDataStep1 = { + category: step1Values.category.map(item => item.name), + class_grade: step1Values.class_grade.name, + title: step1Values.title } - // replace non uploaded images in formData fields with uploaded images + // Add + const formData = { + ...formDataStep1, + ...step === 2 ? step2Values : {}, + publish: step === 2 ? true : false + }; + + if (step === 2) { + let makinStepsImages = fileUploadResponse.filter(item => item.name.startsWith('making_steps')) + let images = fileUploadResponse.filter(item => item.name === 'images') + let materials_used_image = fileUploadResponse.find(item => item.name === 'materials_used_image') + + images = images[0]?.files.map(item => ({ image: replaceImageUrlWithFileUrl(item) })) + formData['images'] = [...(images || []), ...uploadedImages.images.map(item => ({ image: replaceImageUrlWithFileUrl(item) }))] + + if (materials_used_image) { + formData['materials_used_image'] = { + file_url: materials_used_image.files[0].image_url, + public_id: materials_used_image.files[0].public_id + } + } + + if (!materials_used_image && uploadedImages.materials_used_image[0]) { + materials_used_image = uploadedImages.materials_used_image[0] + formData['materials_used_image'] = { + file_url: materials_used_image.file_url, + public_id: materials_used_image.public_id + } + } - // Assign making steps uploaded and formated images to their associated steps - if (makinStepsImages.length > 0) { - const makingStepsWithUploadedImages = formData.making_steps.map((stepData, index) => { - const uploaded = uploadedImages.making_steps[index]; - const data = { - ...stepData, - image: [...makinStepsImages[index].files.map(item => replaceImageUrlWithFileUrl(item)), ...(uploaded || [])], - step_order: index + 1, - }; - delete data.id; - delete data.images; - return data; - }); - formData.making_steps = makingStepsWithUploadedImages; + // replace non uploaded images in formData fields with uploaded images + + // Assign making steps uploaded and formated images to their associated steps + if (makinStepsImages.length > 0) { + const makingStepsWithUploadedImages = formData.making_steps.map((stepData, index) => { + const uploaded = uploadedImages.making_steps[index] + const data = { + ...stepData, + image: [...makinStepsImages[index].files.map(item => replaceImageUrlWithFileUrl(item)), ... (uploaded || [])], + step_order: index + 1 + }; + delete data.id; + delete data.images + return data; + }) + + formData.making_steps = makingStepsWithUploadedImages + } } - } - const activityId = props.match.params?.id; - let createOrUpdateResponse; + const activityId = props.match.params?.id + let createOrUpdateResponse; - if (activityId) { - // API call to the update activity - try { - createOrUpdateResponse = await API.updateActivity(props.auth.token, activityId, formData); - } catch (err) { - toast.error(`Error updating ${err.message}`); + if (props.match.params.id) { + // API call to the update activity + createOrUpdateResponse = await API.updateActivity(props.auth.token, activityId, formData) } - } else { - // API call to the create activity - try { - createOrUpdateResponse = await API.createActivity(props.auth?.token, formData); - } catch (err) { - toast.error(`Error creating activity ${err.message}`); + + else { + // API call to the create activity + createOrUpdateResponse = await API.createActivity(props.auth?.token, formData); } - } - if (createOrUpdateResponse.status === 201) { const data = await createOrUpdateResponse.json(); - toast.success(`Successfully created ${data.title}`); - props.history.replace(`/activities/${data.id}`); - } - if (createOrUpdateResponse.status === 200) { - const data = await createOrUpdateResponse.json(); - toast.success(`Successfully updated ${data.title}`); - props.history.replace(`/activities/${data.id}`); - } -}; + if (data) { + if (!activityId) { + props.history.replace(`/activities/${data.id}/edit`) + } + callback(true) + } + else { + callback(false) + } +} -const replaceImageUrlWithFileUrl = obj => { - if (!('file_url' in obj)) { - obj = { ...obj, file_url: obj.image_url }; - } +const replaceImageUrlWithFileUrl = (obj) => { + if (!('file_url' in obj)) { + obj = { ...obj, file_url: obj.image_url } + } - delete obj.image_url; + delete obj.image_url - return obj; -}; + return obj +} const formFilesUpload = (files, props, state, handleSetState) => { - // The files parametter is in the format - // {[key]:value} where : - // - key represents the field - // - value represents an array of files belonging to the field. - // - Value can also be an object of keys with their arrays values. - // - returns a Promise containing {name:string, files:[]}[] or error + // The files parametter is in the format + // {[key]:value} where : + // - key represents the field + // - value represents an array of files belonging to the field. + // - Value can also be an object of keys with their arrays values. + // - returns a Promise containing {name:string, files:[]}[] or error - let promises = new Map(); + let promises = new Map(); - Object.keys(files).forEach(field => { - const list = files[field]; - const isArrayFilled = Array.isArray(list) && list.length > 0; - const isObjectFilled = !isArrayFilled && Object.keys(list).length > 0; + Object.keys(files).forEach(field => { + const list = files[field]; + const isArrayFilled = Array.isArray(list) && list.length > 0; + const isObjectFilled = !isArrayFilled && Object.keys(list).length > 0; - if (isArrayFilled) { - const fieldFilesUploadPromises = []; + if (isArrayFilled) { + const fieldFilesUploadPromises = []; - list.forEach(file => fieldFilesUploadPromises.push(uploadImage(file, state, props, handleSetState))); - promises.set(field, fieldFilesUploadPromises); - } + list.forEach(file => fieldFilesUploadPromises.push(uploadImage(file, state, props, handleSetState))) + promises.set(field, fieldFilesUploadPromises); + } - if (isObjectFilled) { - Object.keys(list).forEach((key, index) => { - const fieldFilesUploadPromises = []; + if (isObjectFilled) { + Object.keys(list).forEach((key, index) => { + const fieldFilesUploadPromises = []; - list[key].forEach(file => fieldFilesUploadPromises.push(uploadImage(file, state, props, handleSetState))); - promises.set(`${field}[${index}]`, fieldFilesUploadPromises); - }); - } - }); + list[key].forEach(file => fieldFilesUploadPromises.push(uploadImage(file, state, props, handleSetState))); + promises.set(`${field}[${index}]`, fieldFilesUploadPromises); + }) + } + }); - const promisesArray = Array.from(promises.entries()); + const promisesArray = Array.from(promises.entries()); - return Promise.all( - promisesArray.map(([name, promise]) => Promise.all(promise).then(files => ({ name, files }))), - ).catch(err => ({ error: err })); -}; + return Promise.all(promisesArray.map(([name, promise]) => + Promise.all(promise).then(files => ({ name, files })) + )).catch(err => ({ error: err })); +} \ No newline at end of file diff --git a/zubhub_frontend/zubhub/src/views/create_activity/step1/Step1.jsx b/zubhub_frontend/zubhub/src/views/create_activity/step1/Step1.jsx index 694a71448..ceeaa9661 100644 --- a/zubhub_frontend/zubhub/src/views/create_activity/step1/Step1.jsx +++ b/zubhub_frontend/zubhub/src/views/create_activity/step1/Step1.jsx @@ -41,7 +41,6 @@ export default function Step1({ formik, ...props }) { data={categories} onChange={_.debounce(data => formik.setFieldValue('category', data), 200)} selectedItems={formik.values.category} - error={Boolean(formik.errors.category)} // error={formik.touched.category && formik.errors.category} // onBlur={formik.handleBlur} limit={3} @@ -53,7 +52,6 @@ export default function Step1({ formik, ...props }) { placeholder="Select Class grade" name="class_grade" handleChange={value => formik.setFieldValue('class_grade', value)} - error={Boolean(formik.errors.class_grade)} // onBlur={data => { // formik.setTouched({ class_grade: true }); // formik.handleBlur(data); diff --git a/zubhub_frontend/zubhub/src/views/create_activity/step2/Step2.jsx b/zubhub_frontend/zubhub/src/views/create_activity/step2/Step2.jsx index 8520b449b..cecc2f8a4 100644 --- a/zubhub_frontend/zubhub/src/views/create_activity/step2/Step2.jsx +++ b/zubhub_frontend/zubhub/src/views/create_activity/step2/Step2.jsx @@ -13,6 +13,8 @@ const idPrefix = 'activitystep'; const DEFAULT_STEP = { description: '', images: [], title: '', id: uniqueId(idPrefix) }; export default function Step2({ formik, id }) { + // const formik = useFormik(step2Schema); + const classes = makeStyles(step2Styles)(); const commonClasses = makeStyles(styles)(); const [steps, setSteps] = useState([{ ...DEFAULT_STEP }]); diff --git a/zubhub_frontend/zubhub/src/views/my_activities/MyActivities.jsx b/zubhub_frontend/zubhub/src/views/my_activities/MyActivities.jsx deleted file mode 100644 index a808a076c..000000000 --- a/zubhub_frontend/zubhub/src/views/my_activities/MyActivities.jsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { connect, useSelector } from 'react-redux'; -import { - getActivities, - getMyActivities, - activityToggleSave, - setActivity, - getUnPublishedActivities, -} from '../../store/actions/activityActions'; -import 'slick-carousel/slick/slick.css'; -import 'slick-carousel/slick/slick-theme.css'; -import Activity from '../../components/activity/activity'; -import styles from '../../assets/js/styles/views/activities/activitiesStyles'; -import { makeStyles } from '@material-ui/core/styles'; -import ErrorPage from '../error/ErrorPage'; -import DefaultStyles from '../../assets/js/styles'; -import { Grid, Box, Typography } from '@material-ui/core'; -import LoadingPage from '../loading/LoadingPage'; -const useStyles = makeStyles(styles); - -function MyActivities(props) { - const classes = useStyles(); - const [loading, setLoading] = useState(true); - const { activities } = useSelector(state => state); - const [activityList, setActivityList] = useState(props?.activities ?? []); - - const commonClasses = makeStyles(DefaultStyles)(); - - useEffect(() => { - setActivityList(activities.all_activities); - }, [activities]); - - useEffect(() => { - async function fetcher() { - setLoading(true); - await props.getMyActivities(props.auth); - setActivityList(activities.all_activities); - setLoading(false); - } - - fetcher(); - }, []); - - if (loading) { - return ; - } else { - if (!activityList || activityList.length === 0) { - return ; - } else { - return ( -
    - - My Activities - - - {activityList && - activityList.map((activity, index) => ( - - - - ))} - -
    - ); - } - } -} - -const mapStateToProps = state => { - return { - activities: state.activities, - auth: state.auth, - }; -}; - -const mapDispatchToProps = dispatch => { - return { - getActivities: args => { - return dispatch(getActivities(args)); - }, - getMyActivities: args => { - return dispatch(getMyActivities(args)); - }, - getUnPublishedActivities: args => { - return dispatch(getUnPublishedActivities(args)); - }, - activityToggleSave: args => { - return dispatch(activityToggleSave(args)); - }, - setActivity: args => { - return dispatch(setActivity(args)); - }, - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(MyActivities);