From ee7e64fb6f096dd8b2623ec5064154332abbd423 Mon Sep 17 00:00:00 2001 From: Ndibe Raymond Olisaemeka Date: Sat, 18 May 2024 20:36:04 +0100 Subject: [PATCH] fix routing * fix routing * fix pagewrapper * remove previous protected route features and reimplement (was defective) * implement redirection feature ( when you try to visit a protected route, you get redirected to the login page instead of the pagenotfound error, and after logging in you get redirected back to the protected page you initially wanted to visit) Issue: #1127 Signed-off-by: Ndibe Raymond Olisaemeka --- zubhub_frontend/zubhub/src/App.js | 426 ++---------------- zubhub_frontend/zubhub/src/api/api.js | 151 ++----- .../views/page_wrapper/pageWrapperStyles.js | 11 +- .../zubhub/src/assets/js/utils/scripts.js | 141 +++--- .../protected_route/ProtectedRoute.jsx | 11 +- .../zubhub/src/store/actions/authActions.js | 414 ++++++++--------- .../zubhub/src/views/PageWrapper.jsx | 82 ++-- .../zubhub/src/views/login/Login.jsx | 118 ++--- .../zubhub/src/views/login/loginScripts.js | 38 +- .../zubhub/src/views/profile/Profile.jsx | 178 +++----- .../src/views/profile/profileScripts.js | 66 +-- .../zubhub/src/views/signup/Signup.jsx | 285 +++--------- .../zubhub/src/views/signup/signupScripts.js | 63 +-- 13 files changed, 610 insertions(+), 1374 deletions(-) diff --git a/zubhub_frontend/zubhub/src/App.js b/zubhub_frontend/zubhub/src/App.js index 05d7bdef9..09e6149da 100644 --- a/zubhub_frontend/zubhub/src/App.js +++ b/zubhub_frontend/zubhub/src/App.js @@ -3,7 +3,6 @@ import { withTranslation } from 'react-i18next'; import { Routes, Route } from 'react-router-dom'; import { connect } from 'react-redux'; import CreateActivity from './views/create_activity/CreateActivity'; -import LoadingPage from './views/loading/LoadingPage'; import PageWrapper from './views/PageWrapper'; import ZubhubAPI from './api/api'; import { updateTheme } from './theme'; @@ -52,17 +51,8 @@ const Challenge = React.lazy(() => import('./views/challenge/Challenge')); const FAQs = React.lazy(() => import('./views/faqs/FAQs')); const NotFound = React.lazy(() => import('./views/not_found/NotFound')); const Settings = React.lazy(() => import('./views/settings/Settings')); -const API = new ZubhubAPI(); - -const LazyImport = props => { - const { LazyComponent, ...restOfProps } = props; - return ( - }> - - - ); -}; +const API = new ZubhubAPI(); const ThemeContext = React.createContext(); @@ -87,425 +77,105 @@ function App(props) { - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> - - - - } - /> + } /> + } /> - - - } + element={} /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - } + element={} /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> - - - - } - /> + } /> + } /> - - - } + element={} /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> - - - - } - /> + } /> ); diff --git a/zubhub_frontend/zubhub/src/api/api.js b/zubhub_frontend/zubhub/src/api/api.js index 075b8f2dd..31daf09d9 100644 --- a/zubhub_frontend/zubhub/src/api/api.js +++ b/zubhub_frontend/zubhub/src/api/api.js @@ -11,8 +11,8 @@ class API { */ this.domain = process.env.REACT_APP_NODE_ENV === 'production' - ? process.env.REACT_APP_BACKEND_PRODUCTION_URL + '/api/' - : process.env.REACT_APP_BACKEND_DEVELOPMENT_URL + '/api/'; + ? `${process.env.REACT_APP_BACKEND_PRODUCTION_URL}/api/` + : `${process.env.REACT_APP_BACKEND_DEVELOPMENT_URL}/api/`; } /** @@ -26,13 +26,7 @@ class API { * @param {string} content_type - content type to be used for the request * @returns {Promise<>} */ - 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, @@ -52,14 +46,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) { @@ -124,9 +118,6 @@ class API { * @todo - describe method's signature */ logout = token => { - const initialUrl = window.location.href - sessionStorage.setItem('initialUrl', initialUrl) - const url = 'rest-auth/logout/'; const method = 'POST'; return this.request({ url, method, token }).then(res => res.json()); @@ -138,17 +129,7 @@ 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({ @@ -296,7 +277,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()); } @@ -312,7 +293,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()); } @@ -328,7 +309,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()); } @@ -340,7 +321,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()); @@ -356,7 +337,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()); } @@ -372,7 +353,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()); } @@ -388,7 +369,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()); } @@ -404,7 +385,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()); } @@ -422,7 +403,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()); } @@ -439,7 +420,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 { @@ -459,7 +440,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()); } @@ -474,14 +455,14 @@ class API { */ getUserProjects = ({ username, page, limit, token, project_to_omit }) => { let url = `creators/${username}/projects`; - let queryParams = sanitizeObject({ page, limit, project_to_omit }) + const queryParams = sanitizeObject({ page, limit, project_to_omit }); const searchParams = new URLSearchParams(queryParams); url = `${url}?${searchParams}`; return this.request({ url, token }).then(res => res.json()); }; getUserActivity = (username, page) => { - let url = `activitylog/${username}/?page=${page}`; + const url = `activitylog/${username}/?page=${page}`; return this.request({ url }).then(res => res.json()); }; @@ -574,9 +555,7 @@ 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()); }; @@ -588,9 +567,7 @@ 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()); }; @@ -614,8 +591,7 @@ 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'; @@ -663,9 +639,7 @@ 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()); }; @@ -681,9 +655,7 @@ 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()); }; /** @@ -731,18 +703,7 @@ 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({ @@ -765,18 +726,7 @@ 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'; @@ -830,9 +780,7 @@ 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' }), ); }; @@ -862,11 +810,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()); @@ -912,9 +860,7 @@ 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()); }; @@ -1024,10 +970,10 @@ class API { }; /** - * @method getChallenge - * @author Suchakra Sharma - * - */ + * @method getChallenge + * @author Suchakra Sharma + * + */ getChallenge = () => { const url = `challenge/`; @@ -1065,9 +1011,7 @@ 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()); }; @@ -1079,7 +1023,7 @@ class API { * @returns the user's notifications */ getNotifications = (page, token) => { - const url = 'notifications/?' + new URLSearchParams({ page }).toString(); + const url = `notifications/?${new URLSearchParams({ page }).toString()}`; return this.request({ url, token }).then(res => res.json()); }; @@ -1127,7 +1071,7 @@ class API { const body = JSON.stringify(args); return this.request({ url, method, token, body }); - //.then(res => res.json()); + // .then(res => res.json()); }; deleteActivity = ({ token, id }) => { @@ -1136,9 +1080,8 @@ class API { return this.request({ url, method, token }); }; - getActivities = (params) => { - - let queryParams = sanitizeObject(params) + getActivities = params => { + const queryParams = sanitizeObject(params); const searchParams = new URLSearchParams(queryParams); let url = `activities`; url = `${url}?${searchParams}`; diff --git a/zubhub_frontend/zubhub/src/assets/js/styles/views/page_wrapper/pageWrapperStyles.js b/zubhub_frontend/zubhub/src/assets/js/styles/views/page_wrapper/pageWrapperStyles.js index 26b3c931a..3fd2d97b5 100644 --- a/zubhub_frontend/zubhub/src/assets/js/styles/views/page_wrapper/pageWrapperStyles.js +++ b/zubhub_frontend/zubhub/src/assets/js/styles/views/page_wrapper/pageWrapperStyles.js @@ -1,7 +1,10 @@ -import { colors } from "../../../colors"; +import { colors } from '../../../colors'; const styles = theme => ({ - childrenContainer: { padding: '0' }, + childrenContainer: { + padding: '0', + minHeight: '90vh', + }, navBarStyle: { backgroundColor: 'var(--primary-color1)', }, @@ -133,7 +136,7 @@ const styles = theme => ({ username: { color: `${colors.white} !important`, marginBottom: `${0} !important`, - textTransform: 'capitalize' + textTransform: 'capitalize', }, searchFormSubmitStyle: { @@ -166,7 +169,7 @@ const styles = theme => ({ navActionStyle: { display: 'flex', alignItems: 'center', - gap: 20 + gap: 20, }, avatarStyle: { cursor: 'pointer', diff --git a/zubhub_frontend/zubhub/src/assets/js/utils/scripts.js b/zubhub_frontend/zubhub/src/assets/js/utils/scripts.js index b15ea5846..991e09b74 100644 --- a/zubhub_frontend/zubhub/src/assets/js/utils/scripts.js +++ b/zubhub_frontend/zubhub/src/assets/js/utils/scripts.js @@ -21,12 +21,11 @@ export const doConfig = { * * @todo - describe function's signature */ -export const cloudinaryFactory = window => { - return window.cloudinary.Cloudinary.new({ +export const cloudinaryFactory = window => + window.cloudinary.Cloudinary.new({ cloud_name: 'zubhub', secure: true, }); -}; /** * @function buildVideoThumbnailURL @@ -36,7 +35,7 @@ export const cloudinaryFactory = window => { */ export const videoOrUrl = video_url => { - let regex = + const regex = /^((http[s]?:\/\/)?(www\.)?youtube\.com)?((http[s]?:\/\/)?(www\.)?vimeo\.com)?((http[s]?:\/\/)?(www\.)?drive.google\.com)?/gm; return video_url.match(regex)[0] !== '' ? false : true; }; @@ -61,7 +60,7 @@ export const buildVideoThumbnailURL = video_url => { return video_url; } } else { - return video_url + '.jpg'; + return `${video_url}.jpg`; } }; @@ -71,15 +70,13 @@ export const buildVideoThumbnailURL = video_url => { * * @todo - describe function's signature */ -export const getPlayerOptions = (window, video_url) => { - return { - posterOptions: { publicId: buildVideoThumbnailURL(video_url) }, - hideContextMenu: true, - logoImageUrl: logo, - logoOnclickUrl: window.location.origin, - showLogo: true, - }; -}; +export const getPlayerOptions = (window, video_url) => ({ + posterOptions: { publicId: buildVideoThumbnailURL(video_url) }, + hideContextMenu: true, + logoImageUrl: logo, + logoOnclickUrl: window.location.origin, + showLogo: true, +}); /** * @object s3 @@ -113,9 +110,7 @@ const shouldSetImages = (compressed, images, state, handleSetState) => { * * @todo - describe function's signature */ -export const slugify = str => { - return str.replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-'); -}; +export const slugify = str => str.replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-'); /** * @function recursiveCountComments @@ -124,10 +119,10 @@ export const slugify = str => { * @todo - describe function's signature */ const recursiveCountComments = (comments, countArr) => { - for (let comment of comments) { + comments.forEach(comment => { countArr['count'] += 1; recursiveCountComments(comment['replies'], countArr); - } + }); }; /** @@ -149,11 +144,12 @@ export const countComments = comments => { * @todo - describe function's signature */ export const Compress = (images, state, handleSetState) => { - let compressed = []; + const compressed = []; for (let index = 0; index < images.length; index += 1) { - let image = images[index]; + const image = images[index]; if (image && image.type.split('/')[1] !== 'gif') { + // eslint-disable-next-line no-new new Compressor(image, { quality: 0.6, convertSize: 100000, @@ -188,42 +184,42 @@ export const dFormatter = str => { let interval = seconds / 31536000; if (interval > 1) { - let result = Math.round(interval); + const result = Math.round(interval); return { value: result, key: result > 1 ? 'years' : 'year' }; } interval = seconds / 2592000; if (interval > 1) { - let result = Math.round(interval); + const result = Math.round(interval); return { value: result, key: result > 1 ? 'months' : 'month' }; } interval = seconds / 86400; if (interval > 1) { - let result = Math.round(interval); + const result = Math.round(interval); return { value: result, key: result > 1 ? 'days' : 'day' }; } interval = seconds / 3600; if (interval > 1) { - let result = Math.round(interval); + const result = Math.round(interval); return { value: result, key: result > 1 ? 'hours' : 'hour' }; } interval = seconds / 60; if (interval > 1) { - let result = Math.round(interval); + const result = Math.round(interval); return { value: result, key: result > 1 ? 'minutes' : 'minute' }; } - let result = Math.round(interval); + const result = Math.round(interval); return { value: result, key: result > 1 ? 'seconds' : 'second' }; }; export function nFormatter(num) { if (num >= 1000000000) { - return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'G'; + return `${(num / 1000000000).toFixed(1).replace(/\.0$/, '')}G`; } if (num >= 1000000) { - return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; + return `${(num / 1000000).toFixed(1).replace(/\.0$/, '')}M`; } if (num >= 1000) { - return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K'; + return `${(num / 1000).toFixed(1).replace(/\.0$/, '')}K`; } return num; } @@ -235,20 +231,18 @@ export function nFormatter(num) { * @todo - describe function's signature */ export const parseComments = comments => { - for (let each_comment of comments) { + comments.forEach(each_comment => { const mentions = each_comment.text.match(/\B@[a-z0-9_.-]+/gi); if (Array.isArray(mentions)) { - for (let mention of mentions) { + mentions.forEach(mention => { each_comment.text = each_comment.text.replace( mention, - `${mention}`, + `${mention}`, ); - } + }); } parseComments(each_comment['replies']); - } + }); }; /** @@ -258,14 +252,13 @@ export const parseComments = comments => { * @todo - describe function's signature */ export const tempAddComment = (comment, comments, parent_id) => { - for (let each_comment of comments) { + comments.forEach(each_comment => { if (each_comment.id === parent_id) { each_comment.replies.unshift(comment); - break; } else { tempAddComment(comment, each_comment['replies'], parent_id); } - } + }); }; /** @@ -275,7 +268,7 @@ export const tempAddComment = (comment, comments, parent_id) => { * @todo - describe function's signature */ export const tempDeleteComment = (comments, comment_id) => { - for (let index = 0; index < comments.length; index++) { + for (let index = 0; index < comments.length; index += 1) { if (Number(comments[index].id) === Number(comment_id)) { comments.splice(index, 1); break; @@ -293,19 +286,11 @@ export const tempDeleteComment = (comments, comment_id) => { */ export const calculateLabelWidth = (text, document) => { if (text?.length) { - let label = document.evaluate( - `//label[text()='${text}']`, - document, - null, - 0, - null, - ); + let label = document.evaluate(`//label[text()='${text}']`, document, null, 0, null); label = label?.iterateNext(); - let label_width = label?.offsetWidth; + const label_width = label?.offsetWidth; return label_width ? label_width : text?.length; - } else { - return; } }; @@ -317,40 +302,36 @@ export const calculateLabelWidth = (text, document) => { * @param {String} tag - name of tag. * @returns {boolean} */ -export const isBaseTag = tag => { - return BASE_TAGS.includes(tag); -}; - -export const getRouteFieldIndex = str => { - let arr = str.split('.'); - let { route, index } = getRouteAndIndex(arr[0]); - return arr.length > 1 - ? { route: route, field: arr[1], index: index } - : { field: route, index: index }; -}; +export const isBaseTag = tag => BASE_TAGS.includes(tag); export const getRouteAndIndex = str => { - let arr = str.split('['); + const arr = str.split('['); return arr.length > 1 - ? { route: arr[0], index: parseInt(arr[1].split('')[0]) } + ? { route: arr[0], index: parseInt(arr[1].split('')[0], 10) } : { route: arr[0], index: parseInt('-1', 10) }; }; +export const getRouteFieldIndex = str => { + const arr = str.split('.'); + const { route, index } = getRouteAndIndex(arr[0]); + return arr.length > 1 ? { route, field: arr[1], index } : { field: route, index }; +}; + export const getIndexFromFieldName = fieldName => { - let arr = fieldName.split('['); - return arr.length > 1 ? parseInt(arr[1].split('')[0]) : parseInt('-1', 10); + const arr = fieldName.split('['); + return arr.length > 1 ? parseInt(arr[1].split('')[0], 10) : parseInt('-1', 10); }; export const getFieldAndIndex = str => { - let arr = str.split('['); + const arr = str.split('['); return arr.length > 1 - ? { field: arr[0], index: parseInt(arr[1].split('')[0]) } + ? { field: arr[0], index: parseInt(arr[1].split('')[0], 10) } : { field: arr[0], index: parseInt('-1', 10) }; }; -export const getBase64ImageFromURL = (url, field, index) => { - return new Promise((resolve, reject) => { - var img = new Image(); +export const getBase64ImageFromURL = (url, field, index) => + new Promise((resolve, reject) => { + const img = new Image(); img.setAttribute('crossOrigin', 'anonymous'); // function draw(img) { // var buffer = document.createElement('canvas'); @@ -378,17 +359,15 @@ export const getBase64ImageFromURL = (url, field, index) => { // draw(img); // }; img.onload = () => { - var canvas = document.createElement('canvas'); + const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; - var ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d'); ctx.fillStyle = '#FFF'; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); - var dataURL = canvas.toDataURL('image/jpeg'); - index >= 0 - ? resolve({ [`${field}${index}image`]: dataURL }) - : resolve({ [field]: dataURL }); + const dataURL = canvas.toDataURL('image/jpeg'); + index >= 0 ? resolve({ [`${field}${index}image`]: dataURL }) : resolve({ [field]: dataURL }); }; img.onerror = error => { reject(error); @@ -396,9 +375,13 @@ export const getBase64ImageFromURL = (url, field, index) => { img.src = url; }); -}; export const capitalize = str => { - let newStr = str.toString().toLowerCase(); + const newStr = str.toString().toLowerCase(); return newStr.charAt(0).toUpperCase() + newStr.slice(1); }; + +export const getRedirectPath = url => { + url = url.split('redirect='); + return url.length > 1 ? url[1] : ''; +}; diff --git a/zubhub_frontend/zubhub/src/components/protected_route/ProtectedRoute.jsx b/zubhub_frontend/zubhub/src/components/protected_route/ProtectedRoute.jsx index c8f47b7b7..11e1d5e21 100644 --- a/zubhub_frontend/zubhub/src/components/protected_route/ProtectedRoute.jsx +++ b/zubhub_frontend/zubhub/src/components/protected_route/ProtectedRoute.jsx @@ -1,10 +1,7 @@ -import React from 'react' +import React from 'react'; import { Navigate } from 'react-router-dom'; -const ProtectedRoute = props => { - const { wrapper: Wrapper, component, ...rest } = props; +const ProtectedRoute = props => + props.auth?.token ? props.children : ; - return props.auth?.token ? : ; - }; - - export default ProtectedRoute \ No newline at end of file +export default ProtectedRoute; diff --git a/zubhub_frontend/zubhub/src/store/actions/authActions.js b/zubhub_frontend/zubhub/src/store/actions/authActions.js index 7cf8bd586..2dbef3f89 100644 --- a/zubhub_frontend/zubhub/src/store/actions/authActions.js +++ b/zubhub_frontend/zubhub/src/store/actions/authActions.js @@ -1,5 +1,6 @@ -import ZubhubAPI from '../../api'; import { toast } from 'react-toastify'; +import ZubhubAPI from '../../api'; +import { getRedirectPath } from '../../assets/js/utils/scripts'; const API = new ZubhubAPI(); @@ -9,13 +10,11 @@ const API = new ZubhubAPI(); * * @todo - describe function's signature */ -export const setAuthUser = auth_user => { - return dispatch => { - dispatch({ - type: 'SET_AUTH_USER', - payload: auth_user, - }); - }; +export const setAuthUser = auth_user => dispatch => { + dispatch({ + type: 'SET_AUTH_USER', + payload: auth_user, + }); }; /** @@ -25,29 +24,21 @@ export const setAuthUser = auth_user => { * @todo - describe function's signature */ -export const login = args => { - return dispatch => { - return API.login(args.values) - .then(res => { - if (!res.key) { - throw new Error(JSON.stringify(res)); - } - dispatch({ - type: 'SET_AUTH_USER', - payload: { token: res.key }, - }); - }) - .then(() => { - const initialUrl = sessionStorage.getItem('initialUrl'); - if (initialUrl) { - sessionStorage.removeItem('initialUrl') - window.location.href = initialUrl; - } else { - args.navigate('/'); - } +export const login = args => dispatch => + API.login(args.values) + .then(res => { + if (!res.key) { + throw new Error(JSON.stringify(res)); + } + dispatch({ + type: 'SET_AUTH_USER', + payload: { token: res.key }, }); - }; -}; + }) + .then(() => { + const redirectPath = getRedirectPath(args.location.search); + args.navigate(redirectPath || '/'); + }); /** * @function logout @@ -55,30 +46,27 @@ export const login = args => { * * @todo - describe function's signature */ -export const logout = args => { - return dispatch => { - return API.logout(args.token) - .then(_ => { - dispatch({ - type: 'SET_AUTH_USER', - payload: { - token: null, - username: null, - id: null, - avatar: null, - members_count: null, - tags: [], - }, - }); - }) - .then(_ => { - args.navigate('/'); - }) - .catch(_ => { - toast.warning(args.t('pageWrapper.errors.logoutFailed')); +export const logout = args => dispatch => + API.logout(args.token) + .then(() => { + dispatch({ + type: 'SET_AUTH_USER', + payload: { + token: null, + username: null, + id: null, + avatar: null, + members_count: null, + tags: [], + }, }); - }; -}; + }) + .then(() => { + args.navigate('/'); + }) + .catch(() => { + toast.warning(args.t('pageWrapper.errors.logoutFailed')); + }); /** * @function getAuthUser @@ -86,48 +74,42 @@ export const logout = args => { * * @todo - describe function's signature */ -export const getAuthUser = props => { - return dispatch => { - return API.getAuthUser(props.auth.token) - .then(res => { - if (!res.id) { - dispatch( - logout({ - token: props.auth.token, - navigate: props.navigate, - t: props.t, - }), - ).then(() => { - props.navigate('/account-status'); - }); - throw new Error(props.t('pageWrapper.errors.unexpected')); - } else { - dispatch({ - type: 'SET_AUTH_USER', - payload: { - ...props.auth, - username: res.username, - id: res.id, - avatar: res.avatar, - members_count: res.members_count, - tags: res.tags, - }, - }); - } +export const getAuthUser = props => dispatch => + API.getAuthUser(props.auth.token) + .then(res => { + if (!res.id) { + dispatch( + logout({ + token: props.auth.token, + navigate: props.navigate, + t: props.t, + }), + ).then(() => { + props.navigate('/account-status'); + }); + throw new Error(props.t('pageWrapper.errors.unexpected')); + } else { + dispatch({ + type: 'SET_AUTH_USER', + payload: { + ...props.auth, + username: res.username, + id: res.id, + avatar: res.avatar, + members_count: res.members_count, + tags: res.tags, + }, + }); + } - return res; - }) - .catch(error => toast.warning(error.message)); - }; -}; + return res; + }) + .catch(error => toast.warning(error.message)); -export const AccountStatus = args => { - return () => { - return API.getAccountStatus(args.token).catch(() => { - toast.warning(args.t('pageWrapper.errors.unexpected')); - }); - }; -}; +export const AccountStatus = args => () => + API.getAccountStatus(args.token).catch(() => { + toast.warning(args.t('pageWrapper.errors.unexpected')); + }); /** * @function signup @@ -135,21 +117,21 @@ export const AccountStatus = args => { * * @todo - describe function's signature */ -export const signup = args => { - return dispatch => { - return API.signup(args.values) - .then(res => { - if (!res.key) { - throw new Error(JSON.stringify(res)); - } - dispatch({ - type: 'SET_AUTH_USER', - payload: { token: res.key }, - }); - }) - .then(() => args.navigate('/profile')); - }; -}; +export const signup = args => dispatch => + API.signup(args.values) + .then(res => { + if (!res.key) { + throw new Error(JSON.stringify(res)); + } + dispatch({ + type: 'SET_AUTH_USER', + payload: { token: res.key }, + }); + }) + .then(() => { + const redirectPath = getRedirectPath(args.location.search); + args.navigate(redirectPath || '/profile'); + }); /** * @function sendEmailConfirmation @@ -157,20 +139,17 @@ export const signup = args => { * * @todo - describe function's signature */ -export const sendEmailConfirmation = args => { - return () => { - return API.sendEmailConfirmation(args.key).then(res => { - if (res.detail !== 'ok') { - throw new Error(res.detail); - } else { - toast.success(args.t('emailConfirm.toastSuccess')); - setTimeout(() => { - args.navigate('/'); - }, 4000); - } - }); - }; -}; +export const sendEmailConfirmation = args => () => + API.sendEmailConfirmation(args.key).then(res => { + if (res.detail !== 'ok') { + throw new Error(res.detail); + } else { + toast.success(args.t('emailConfirm.toastSuccess')); + setTimeout(() => { + args.navigate('/'); + }, 4000); + } + }); /** * @function sendPhoneConfirmation @@ -178,20 +157,17 @@ export const sendEmailConfirmation = args => { * * @todo - describe function's signature */ -export const sendPhoneConfirmation = args => { - return () => { - return API.sendPhoneConfirmation(args.key).then(res => { - if (res.detail !== 'ok') { - throw new Error(res.detail); - } else { - toast.success(args.t('phoneConfirm.toastSuccess')); - setTimeout(() => { - args.navigate('/'); - }, 4000); - } - }); - }; -}; +export const sendPhoneConfirmation = args => () => + API.sendPhoneConfirmation(args.key).then(res => { + if (res.detail !== 'ok') { + throw new Error(res.detail); + } else { + toast.success(args.t('phoneConfirm.toastSuccess')); + setTimeout(() => { + args.navigate('/'); + }, 4000); + } + }); /** * @function sendPasswordResetLink @@ -199,20 +175,17 @@ export const sendPhoneConfirmation = args => { * * @todo - describe function's signature */ -export const sendPasswordResetLink = args => { - return () => { - return API.sendPasswordResetLink(args.email).then(res => { - if (res.detail !== 'ok') { - throw new Error(JSON.stringify(res)); - } else { - toast.success(args.t('passwordReset.toastSuccess')); - setTimeout(() => { - args.navigate('/'); - }, 4000); - } - }); - }; -}; +export const sendPasswordResetLink = args => () => + API.sendPasswordResetLink(args.email).then(res => { + if (res.detail !== 'ok') { + throw new Error(JSON.stringify(res)); + } else { + toast.success(args.t('passwordReset.toastSuccess')); + setTimeout(() => { + args.navigate('/'); + }, 4000); + } + }); /** * @function passwordResetConfirm @@ -220,20 +193,17 @@ export const sendPasswordResetLink = args => { * * @todo - describe function's signature */ -export const passwordResetConfirm = args => { - return () => { - return API.passwordResetConfirm(args).then(res => { - if (res.detail !== 'ok') { - throw new Error(JSON.stringify(res)); - } else { - toast.success(args.t('passwordResetConfirm.toastSuccess')); - setTimeout(() => { - args.navigate('/login'); - }, 4000); - } - }); - }; -}; +export const passwordResetConfirm = args => () => + API.passwordResetConfirm(args).then(res => { + if (res.detail !== 'ok') { + throw new Error(JSON.stringify(res)); + } else { + toast.success(args.t('passwordResetConfirm.toastSuccess')); + setTimeout(() => { + args.navigate('/login'); + }, 4000); + } + }); /** * @function getLocations @@ -241,30 +211,27 @@ export const passwordResetConfirm = args => { * * @todo - describe function's signature */ -export const getLocations = args => { - return () => { - return API.getLocations() - .then(res => { - if (Array.isArray(res) && res.length > 0 && res[0].name) { - return { locations: res }; - } else { - res = Object.keys(res) - .map(key => res[key]) - .join('\n'); - throw new Error(res); - } - }) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - error: args.t('signup.errors.unexpected'), - }; - } else { - return { error: error.message }; - } - }); - }; -}; +export const getLocations = args => () => + API.getLocations() + .then(res => { + if (Array.isArray(res) && res.length > 0 && res[0].name) { + return { locations: res }; + } else { + res = Object.keys(res) + .map(key => res[key]) + .join('\n'); + throw new Error(res); + } + }) + .catch(error => { + if (error.message.startsWith('Unexpected')) { + return { + error: args.t('signup.errors.unexpected'), + }; + } else { + return { error: error.message }; + } + }); /** * @function deleteAccount @@ -272,28 +239,25 @@ export const getLocations = args => { * * @todo - describe function's signature */ -export const deleteAccount = args => { - return () => { - return API.deleteAccount(args) - .then(res => { - if (res.detail !== 'ok') { - throw new Error(res.detail); - } else { - toast.success(args.t('profile.delete.toastSuccess')); - args.logout(args); - } - }) - .catch(error => { - if (error.message.startsWith('Unexpected')) { - return { - dialog_error: args.t('profile.delete.errors.unexpected'), - }; - } else { - return { dialog_error: error.message }; - } - }); - }; -}; +export const deleteAccount = args => () => + API.deleteAccount(args) + .then(res => { + if (res.detail !== 'ok') { + throw new Error(res.detail); + } else { + toast.success(args.t('profile.delete.toastSuccess')); + args.logout(args); + } + }) + .catch(error => { + if (error.message.startsWith('Unexpected')) { + return { + dialog_error: args.t('profile.delete.errors.unexpected'), + }; + } else { + return { dialog_error: error.message }; + } + }); /** * @function getSignature @@ -301,20 +265,18 @@ export const deleteAccount = args => { * * @todo - describe function's signature */ -export const getSignature = args => { - return () => { - const t = args.t; - delete args.t; - return API.getSignature(args) - .then(res => { - if (!res.signature) { - throw new Error(); - } else { - return res; - } - }) - .catch(() => { - toast.warning(t('createProject.errors.unexpected')); - }); - }; +export const getSignature = args => () => { + const t = args.t; + delete args.t; + return API.getSignature(args) + .then(res => { + if (!res.signature) { + throw new Error(); + } else { + return res; + } + }) + .catch(() => { + toast.warning(t('createProject.errors.unexpected')); + }); }; diff --git a/zubhub_frontend/zubhub/src/views/PageWrapper.jsx b/zubhub_frontend/zubhub/src/views/PageWrapper.jsx index 728dbf2ab..1ea4712af 100644 --- a/zubhub_frontend/zubhub/src/views/PageWrapper.jsx +++ b/zubhub_frontend/zubhub/src/views/PageWrapper.jsx @@ -37,7 +37,7 @@ import commonStyles from '../assets/js/styles'; import languageMap from '../assets/js/languageMap.json'; import DashboardLayout from '../layouts/DashboardLayout/DashboardLayout'; import Navbar from '../components/Navbar/Navbar'; -import NotFoundPage from './not_found/NotFound'; +import { ProtectedRoute } from '../components'; const useStyles = makeStyles(styles); const useCommonStyles = makeStyles(commonStyles); @@ -52,17 +52,9 @@ function PageWrapper(props) { const backToTopEl = useRef(null); const [prevScrollPos, setPrevScrollPos] = useState(window.pageYOffset); const [isVisible, setIsVisible] = useState(false); - const navigate = useNavigate(); const classes = useStyles(); const common_classes = useCommonStyles(); const trigger = useScrollTrigger(); - const params = useParams(); - const location = useLocation(); - const routeProps = { - location, - params, - navigate, - }; const [state, setState] = React.useState({ loading: false, @@ -107,14 +99,18 @@ function PageWrapper(props) { }, [trigger]); const { loading } = state; - const { t } = props; + const { t, Component } = props; const { zubhub, hero } = props.projects; - // TODO: remove childrenRenderer and use children directly. this will likely mean having useNavigate, useParams, - // useLocation in every component that needs them. - // React.cloneElement makes our code brittle: see https://react.dev/reference/react/cloneElement - const childrenRenderer = () => - React.Children.map(props.children, child => React.cloneElement(child, { ...props, ...routeProps })); + const navigate = useNavigate(); + const params = useParams(); + const location = useLocation(); + const routeProps = { + location, + params, + navigate, + }; + return ( <> @@ -124,44 +120,26 @@ function PageWrapper(props) { - {props.auth?.token ? {loading ? : childrenRenderer()} : null} - {!props.auth?.token && - ![ - '/', - '/signup', - '/login', - '/projects/:id', - '/ambassadors', - '/creators/:username', - '/privacy_policy', - '/terms_of_use', - '/about', - '/challenge', - '/password-reset', - '/email-confirm', - '/password-reset-confirm', - ].includes(location?.pathname) && ( -
- -
- )} + {loading ? ( + + ) : props.auth?.token ? ( + + }> + + + + ) : props.protected ? ( + + }> + + + + ) : ( + }> + + + )}
- {!props.auth?.token && - [ - '/', - '/signup', - '/login', - '/password-reset', - '/projects/:id', - '/ambassadors', - '/creators/:username', - '/privacy_policy', - '/terms_of_use', - '/about', - '/challenge', - '/email-confirm', - '/password-reset-confirm', - ].includes(location?.pathname) &&
{childrenRenderer()}
}