From 24d61296a66db46aa0fbafa60e78c93d004c40cd Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Thu, 29 Oct 2020 01:27:14 +0530 Subject: [PATCH 1/7] add: recent urls component with logic --- client/src/App.js | 10 +- client/src/components/auth/Login.js | 2 + client/src/components/dashboard/Graph.js | 1 + .../dashboard/RecentUrlList/index.css | 10 ++ .../dashboard/RecentUrlList/index.js | 127 ++++++++++++++++++ .../dashboard/RecentUrlcontainer.js | 41 ++++++ client/src/components/dashboard/index.js | 63 +++++++++ client/src/graphQl/queries/getUrlsQuery.js | 27 ++++ client/src/graphQl/queries/loginQuery.js | 2 +- client/src/redux/actionTypes.js | 1 + client/src/redux/rootReducer.js | 3 +- client/src/redux/slices/urls.js | 32 +++++ package.json | 2 +- redirection-server/middlewares/redirect.js | 3 +- redirection-server/models/Url.js | 20 ++- redirection-server/package-lock.json | 3 +- server/graphQl/resolvers.js | 21 ++- server/graphQl/schema.js | 48 +++---- server/index.js | 1 + server/models/Url.js | 20 ++- server/package-lock.json | 5 + server/package.json | 1 + 22 files changed, 406 insertions(+), 37 deletions(-) create mode 100644 client/src/components/dashboard/Graph.js create mode 100644 client/src/components/dashboard/RecentUrlList/index.css create mode 100644 client/src/components/dashboard/RecentUrlList/index.js create mode 100644 client/src/components/dashboard/RecentUrlcontainer.js create mode 100644 client/src/components/dashboard/index.js create mode 100644 client/src/graphQl/queries/getUrlsQuery.js create mode 100644 client/src/redux/slices/urls.js diff --git a/client/src/App.js b/client/src/App.js index 312b71c..2ca2448 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -12,6 +12,8 @@ import { fetchIsDrawerOpen } from './redux/slices/drawer'; import { fetchLogin } from './redux/slices/auth'; import './App.css'; +import Dashboard from './components/dashboard'; +import { fetchUrls } from './redux/slices/urls'; const useStyles = makeStyles((theme) => ({ root: { @@ -39,12 +41,13 @@ function App() { const dispatch = useDispatch(); const snackbarState = useSelector((state) => state.snackbar); - + const token = useSelector((state) => state.auth.authToken); // Fetch the initial drawer open state useEffect(() => { dispatch(fetchIsDrawerOpen()); dispatch(fetchLogin()); - }, [dispatch]); + dispatch(fetchUrls({ token: token })); + }, [dispatch, token]); return (
@@ -62,6 +65,9 @@ function App() { + + + {/* Auth related routes */} diff --git a/client/src/components/auth/Login.js b/client/src/components/auth/Login.js index 3343c0e..9ac844d 100644 --- a/client/src/components/auth/Login.js +++ b/client/src/components/auth/Login.js @@ -20,6 +20,7 @@ import axios from '../../utils/axios'; import loginQuery from '../../graphQl/queries/loginQuery'; import { setIsSnackbarOpen } from '../../redux/slices/snackbar'; import { setLogin } from '../../redux/slices/auth'; +import { fetchUrls } from '../../redux/slices/urls'; // Define styles for this component const useStyles = makeStyles((theme) => ({ @@ -108,6 +109,7 @@ function Login() { // Get the jwt and store in redux and local storage const authToken = response.data.data.login.token; dispatch(setLogin({ isLoggedIn: true, authToken })); + dispatch(fetchUrls({ token: authToken })); history.replace(prevPath, location.state); } catch (err) { diff --git a/client/src/components/dashboard/Graph.js b/client/src/components/dashboard/Graph.js new file mode 100644 index 0000000..aaf4046 --- /dev/null +++ b/client/src/components/dashboard/Graph.js @@ -0,0 +1 @@ +import React from 'react'; diff --git a/client/src/components/dashboard/RecentUrlList/index.css b/client/src/components/dashboard/RecentUrlList/index.css new file mode 100644 index 0000000..2c1d90c --- /dev/null +++ b/client/src/components/dashboard/RecentUrlList/index.css @@ -0,0 +1,10 @@ +.item{ + cursor: 'pointer'; + height: '12%'; +} +.active{ + overflow: 'visible'; + width: '100%'; + background-color: slategray; + color: white; +} \ No newline at end of file diff --git a/client/src/components/dashboard/RecentUrlList/index.js b/client/src/components/dashboard/RecentUrlList/index.js new file mode 100644 index 0000000..ae51e46 --- /dev/null +++ b/client/src/components/dashboard/RecentUrlList/index.js @@ -0,0 +1,127 @@ +/* eslint-disable no-invalid-this */ +/* eslint-disable react/prop-types */ +import { makeStyles } from '@material-ui/core'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchUrls } from '../../../redux/slices/urls'; +import './index.css'; +const useStyles = makeStyles((theme) => ({ + list: { + display: 'flex', + flexDirection: 'column', + width: '100%', + height: '100%', + }, +})); +const UrlItem = ({ url, onClick, active }) => { + const createdAt = new Date(url.createdAt).toDateString().substring(4); + const clicksLength = url.clicks ? url.clicks.length : 0; + return ( +
+
+

{createdAt}

+
+
+

{url.title}

+
+
+

{url.shortUrl}

+
+
+

{clicksLength}

+
+
+ ); +}; +function sortUrls(urls) { + return urls.slice().sort((a, b) => { + return new Date(b.createdAt) - new Date(a.createdAt); + }); +} +const RecentUrlList = (props) => { + const [selected, setSelected] = useState(0); + const classes = useStyles(); + const dispatch = useDispatch(); + const token = useSelector((state) => state.auth.authToken); + const fetchingUrls = useCallback(() => { + dispatch(fetchUrls({ token })); + }, [dispatch, token]); + const urls = useSelector((state) => state.urls.urls); + let UrlsbyRecency = useMemo(sortUrls.bind(this, urls), [sortUrls, urls]); + UrlsbyRecency = UrlsbyRecency.slice(0, 5); + + useEffect(() => { + fetchingUrls(); + }, [fetchingUrls]); + + return ( +
+ +
+

Date

+
+
+

Title

+
+
+

Short Url

+
+
+

Clicks

+
+
+ {UrlsbyRecency.map((url, index) => { + return { + return setSelected(index); + }}/>; + })} +
+ ); +}; + +export default RecentUrlList; diff --git a/client/src/components/dashboard/RecentUrlcontainer.js b/client/src/components/dashboard/RecentUrlcontainer.js new file mode 100644 index 0000000..00deca8 --- /dev/null +++ b/client/src/components/dashboard/RecentUrlcontainer.js @@ -0,0 +1,41 @@ +import { + Card, + CardActions, + CardHeader, + makeStyles, +} from '@material-ui/core'; +import React from 'react'; +import RecentUrlList from './RecentUrlList'; +const useStyles = makeStyles((theme) => ({ + container: { + width: '60%', + height: '100%', + }, +})); + +const RecentUrlContainer = (props) => { + const classes = useStyles(); + return ( +
+ + + + + + +
); +}; + +export default RecentUrlContainer; diff --git a/client/src/components/dashboard/index.js b/client/src/components/dashboard/index.js new file mode 100644 index 0000000..890508d --- /dev/null +++ b/client/src/components/dashboard/index.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { + CssBaseline, + makeStyles, +} from '@material-ui/core'; +import RecentUrlContainer from './RecentUrlcontainer'; +const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexDirection: 'column', + height: '100%', + width: '80%', + padding: '20px', + }, + toproot: { + display: 'flex', + flexDirection: 'row', + height: '40%', + justifyContent: 'space-between', + }, + bottomroot: { + display: 'flex', + flexDirection: 'row', + height: '60%', + justifyContent: 'space-between', + }, + recentUrlcontainer: { + width: '30%', + textAlign: 'center', + }, + leftview: { + width: '30%', + }, + rightview: { + width: '70%', + }, +})); + +export default function Dashboard() { + const classes = useStyles(); + return ( +
+ +
+
+

My URls

+
+
+

Details

+
+
+
+ +
+

Total clicks in the last month

+
+
+

Recently added urls

+
+
+
+ ); +}; diff --git a/client/src/graphQl/queries/getUrlsQuery.js b/client/src/graphQl/queries/getUrlsQuery.js new file mode 100644 index 0000000..d16b0ca --- /dev/null +++ b/client/src/graphQl/queries/getUrlsQuery.js @@ -0,0 +1,27 @@ + +const getUrlsQuery = () => { + const graphqlQuery = { + query: `query{ + getUrls{ + urls{ + owner + _id + title + longUrl + shortUrl + createdAt + updatedAt + clicks{ + ip + time + location{ + city + country + } + } + } + } +}` }; + return graphqlQuery; +}; +export default getUrlsQuery; diff --git a/client/src/graphQl/queries/loginQuery.js b/client/src/graphQl/queries/loginQuery.js index 2a39ac4..b9fa0a2 100644 --- a/client/src/graphQl/queries/loginQuery.js +++ b/client/src/graphQl/queries/loginQuery.js @@ -1,4 +1,4 @@ -const loginQuery=(authData)=>{ +const loginQuery = (authData) => { const graphqlQuery = { query: ` query{ diff --git a/client/src/redux/actionTypes.js b/client/src/redux/actionTypes.js index 695696f..12f5be1 100644 --- a/client/src/redux/actionTypes.js +++ b/client/src/redux/actionTypes.js @@ -2,3 +2,4 @@ export const SET_IS_DRAWER_OPEN = 'SET_IS_DRAWER_OPEN'; export const FETCH_IS_DRAWER_OPEN = 'FETCH_IS_DRAWER_OPEN'; export const LOGIN = 'LOGIN'; export const FETCH_LOGIN = 'FETCH_LOGIN'; +export const FETCH_URLS = 'FETCH_URLS'; diff --git a/client/src/redux/rootReducer.js b/client/src/redux/rootReducer.js index 26bc3b5..1bc22b3 100644 --- a/client/src/redux/rootReducer.js +++ b/client/src/redux/rootReducer.js @@ -2,9 +2,10 @@ import { combineReducers } from 'redux'; import { drawerSlice } from './slices/drawer'; import { authSlice } from './slices/auth'; import { snackbarSlice } from './slices/snackbar'; - +import { urlSlice } from './slices/urls'; export default combineReducers({ [drawerSlice.name]: drawerSlice.reducer, [authSlice.name]: authSlice.reducer, [snackbarSlice.name]: snackbarSlice.reducer, + [urlSlice.name]: urlSlice.reducer, }); diff --git a/client/src/redux/slices/urls.js b/client/src/redux/slices/urls.js new file mode 100644 index 0000000..924d0e4 --- /dev/null +++ b/client/src/redux/slices/urls.js @@ -0,0 +1,32 @@ +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import getUrlsQuery from '../../graphQl/queries/getUrlsQuery'; +import axios from '../../utils/axios'; + +import { FETCH_URLS } from '../actionTypes'; + +export const fetchUrls = createAsyncThunk(FETCH_URLS, + async (payload) => { + try { + const { token } = payload; + const graphqlQuery = getUrlsQuery(); + const response = await axios.post('/', graphqlQuery, { + headers: { + Authorization: token, + }, + }); + const urls = response.data.data.getUrls.urls; + return { urls }; + } catch (err) { + return { urls: [] }; + } + }); +export const urlSlice = createSlice({ + name: 'urls', + initialState: { urls: [] }, + extraReducers: { + [fetchUrls.fulfilled]: (state, action) => ({ + ...state, + ...action.payload, + }), + }, +}); diff --git a/package.json b/package.json index 5698e01..49bbb25 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "install": "concurrently --kill-others-on-fail \"cd ./client && npm install\" \"cd ./server && npm install\" \"cd ./redirection-server && npm install\"", "start-client": "cd ./client && npm start", "start-server": "cd ./server && npm start", - "start-redirection-server":"cd ./redirection-server && npm start", + "start-redirection-server": "cd ./redirection-server && npm start", "dev": "concurrently --kill-others-on-fail \"npm run start-server\" \"npm run start-client\" \"npm run start-redirection-server\"", "start": "npm run start-server", "test": "echo \"Error: no test specified\" && exit 1" diff --git a/redirection-server/middlewares/redirect.js b/redirection-server/middlewares/redirect.js index 0e53c16..e6b7052 100644 --- a/redirection-server/middlewares/redirect.js +++ b/redirection-server/middlewares/redirect.js @@ -46,11 +46,10 @@ const redirect = async (req, res) => { const apiEndpoint = BASE_URL + ip + '?access_key=' + key; const location = await axios.get(apiEndpoint); const click = new Click({ - location: location.data, + location: { city: location.data, country: location.data.country_name }, time, ip, }); - await click.save(); return Url.updateOne({ shortUrl: hash }, { $push: { clicks: click, diff --git a/redirection-server/models/Url.js b/redirection-server/models/Url.js index 91996ca..23c0f2c 100644 --- a/redirection-server/models/Url.js +++ b/redirection-server/models/Url.js @@ -12,9 +12,25 @@ const urlSchema = new Schema( type: String, required: true, }, + title: { + type: String, + }, + description: { + type: String, + }, clicks: [{ - type: mongoose.Types.ObjectId, - ref: 'Click', + location: { + type: 'Object', + required: true, + }, + time: { + type: String, + required: true, + }, + ip: { + type: String, + required: true, + }, }], owner: { type: mongoose.Types.ObjectId, diff --git a/redirection-server/package-lock.json b/redirection-server/package-lock.json index 7e039ac..3886437 100644 --- a/redirection-server/package-lock.json +++ b/redirection-server/package-lock.json @@ -1015,8 +1015,7 @@ "follow-redirects": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", - "dev": true + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" }, "forwarded": { "version": "0.1.2", diff --git a/server/graphQl/resolvers.js b/server/graphQl/resolvers.js index c0df23e..3605e56 100644 --- a/server/graphQl/resolvers.js +++ b/server/graphQl/resolvers.js @@ -4,6 +4,11 @@ const urlShortener = require('../utils/urlShortener'); const validator = require('../validator'); const User = require('../models/User'); const Url = require('../models/Url'); +const { GraphQLDateTime } = require('graphql-iso-date'); + +const customScalarResolver = { + Date: GraphQLDateTime, +}; // Sign in resolver const signUp = async ({ UserInput }) => { @@ -194,6 +199,20 @@ const addDetails = async ({ title, description, shortUrl, updatedShortUrl }, req }; } }; + +// Resolver to fetch all the urls belonging to the current user +const getUrls = async ({ }, request) => { + // Get the user id from the request + const { userId } = request; + if (!userId) { + const error = new Error('Please Login'); + error.code = 407; + throw error; + } + const urls = await Url.find({ owner: userId }); + // Retrieve all the urls owned by the user + return { urls }; +}; module.exports = { - signUp, login, shortenUrl, addDetails, + signUp, login, shortenUrl, addDetails, getUrls, customScalarResolver, }; diff --git a/server/graphQl/schema.js b/server/graphQl/schema.js index 366dd21..b747fed 100644 --- a/server/graphQl/schema.js +++ b/server/graphQl/schema.js @@ -1,34 +1,37 @@ const { buildSchema } = require('graphql'); module.exports = buildSchema(` + scalar Date type Url{ - _id : ID! - ownerid : ID! + _id : String! + owner : String! longUrl : String! shortUrl : String! - expirydate : String! - createdAt : String! - readwriteaccess : Access! + expirydate : Date! + title : String + description : String + createdAt : Date! + updatedAt : Date! + readaccess : [User] + writeaccess : [User] clicks : [Click!] } type User{ - _id : ID! + _id : String! name : String! email : String! password : String! - createdAt : String! - updatedAt : String! - urls : [Url!] + createdAt : Date! + updatedAt : Date! } - - type Click{ - email : String! - clickedAt : String! - location : String! + type location{ + city : String + country : String } - type Access{ - email : String! - permissions : [String!]! + type Click{ + ip : String! + time : Date! + location : location! } type AuthToken{ token : String! @@ -41,21 +44,20 @@ module.exports = buildSchema(` email : String! password : String! } - type PostData{ - urls : [Url!]! - totalurls : Int! - } type RootQuery{ login(email : String! , password : String!) : AuthToken! - urls : PostData! + getUrls: urls! } type shortUrl{ - _id : ID!, + _id : String!, longUrl : String!, shortUrl : String!, title : String, description : String } + type urls{ + urls : [Url!] + } type RootMutation{ signUp(UserInput : UserInputData) : GenericMessage! shortenUrl(longUrl : String!) : shortUrl! diff --git a/server/index.js b/server/index.js index e3e115d..d0164fa 100644 --- a/server/index.js +++ b/server/index.js @@ -63,6 +63,7 @@ mongoose app.listen(PORT, () => console.log(`Listening on port ${PORT}. /graphql for GraphiQl`)); }) .catch((err) => { + console.log(err); // If not connected, exit the process // eslint-disable-next-line no-console console.log('Error while connecting to mongodb: ', err); diff --git a/server/models/Url.js b/server/models/Url.js index 91996ca..23c0f2c 100644 --- a/server/models/Url.js +++ b/server/models/Url.js @@ -12,9 +12,25 @@ const urlSchema = new Schema( type: String, required: true, }, + title: { + type: String, + }, + description: { + type: String, + }, clicks: [{ - type: mongoose.Types.ObjectId, - ref: 'Click', + location: { + type: 'Object', + required: true, + }, + time: { + type: String, + required: true, + }, + ip: { + type: String, + required: true, + }, }], owner: { type: mongoose.Types.ObjectId, diff --git a/server/package-lock.json b/server/package-lock.json index cc21b20..efb8020 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1380,6 +1380,11 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz", "integrity": "sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==" }, + "graphql-iso-date": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz", + "integrity": "sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q==" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", diff --git a/server/package.json b/server/package.json index 1a99aae..45f19a7 100644 --- a/server/package.json +++ b/server/package.json @@ -16,6 +16,7 @@ "express-graphql": "^0.11.0", "express-validator": "^6.6.1", "graphql": "^15.3.0", + "graphql-iso-date": "^3.6.1", "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.0.0", "mongodb": "^3.6.2", From a7ac86c39078214bd9efeea68118a5a03f40ee59 Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Sun, 1 Nov 2020 01:00:18 +0530 Subject: [PATCH 2/7] added charts to dashboard --- client/package-lock.json | 40 +++++++++++++ client/package.json | 2 + client/src/components/dashboard/Graph.js | 1 - .../dashboard/RecentUrlList/index.js | 24 +++----- .../dashboard/RecentUrlcontainer.js | 2 +- client/src/components/dashboard/index.js | 57 +++++++------------ client/src/graphQl/queries/getUrlsQuery.js | 2 +- client/src/redux/actionTypes.js | 1 + client/src/redux/slices/urls.js | 17 +++++- redirection-server/middlewares/redirect.js | 7 ++- server/graphQl/resolvers.js | 11 +++- server/graphQl/schema.js | 2 +- 12 files changed, 105 insertions(+), 61 deletions(-) delete mode 100644 client/src/components/dashboard/Graph.js diff --git a/client/package-lock.json b/client/package-lock.json index eec1c19..402d156 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -3595,6 +3595,32 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, "chokidar": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", @@ -8555,6 +8581,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -10662,6 +10693,15 @@ "whatwg-fetch": "^3.0.0" } }, + "react-chartjs-2": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.10.0.tgz", + "integrity": "sha512-1MjWEkUn8LLFf6GVyYUOrruJTW3yVU5hlEJOwGj3MiokuC+jH/BahjWVGAMonbe9UYbEIUbd2Rn36iVlC0Hb7w==", + "requires": { + "lodash": "^4.17.19", + "prop-types": "^15.7.2" + } + }, "react-dev-utils": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", diff --git a/client/package.json b/client/package.json index 02e00fb..f94420b 100644 --- a/client/package.json +++ b/client/package.json @@ -11,10 +11,12 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "axios": "^0.20.0", + "chart.js": "^2.9.4", "graphql": "^15.3.0", "jwt-decode": "^3.0.0", "prop-types": "^15.7.2", "react": "^16.13.1", + "react-chartjs-2": "^2.10.0", "react-dom": "^16.13.1", "react-redux": "^7.2.1", "react-router-dom": "^5.2.0", diff --git a/client/src/components/dashboard/Graph.js b/client/src/components/dashboard/Graph.js deleted file mode 100644 index aaf4046..0000000 --- a/client/src/components/dashboard/Graph.js +++ /dev/null @@ -1 +0,0 @@ -import React from 'react'; diff --git a/client/src/components/dashboard/RecentUrlList/index.js b/client/src/components/dashboard/RecentUrlList/index.js index ae51e46..4b125db 100644 --- a/client/src/components/dashboard/RecentUrlList/index.js +++ b/client/src/components/dashboard/RecentUrlList/index.js @@ -1,9 +1,9 @@ /* eslint-disable no-invalid-this */ /* eslint-disable react/prop-types */ import { makeStyles } from '@material-ui/core'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchUrls } from '../../../redux/slices/urls'; +import { fetchUrls, setSelectedUrl } from '../../../redux/slices/urls'; import './index.css'; const useStyles = makeStyles((theme) => ({ list: { @@ -19,11 +19,11 @@ const UrlItem = ({ url, onClick, active }) => { return (
{
); }; -function sortUrls(urls) { - return urls.slice().sort((a, b) => { - return new Date(b.createdAt) - new Date(a.createdAt); - }); -} const RecentUrlList = (props) => { - const [selected, setSelected] = useState(0); + const [selected, setSelected] = useState(); const classes = useStyles(); const dispatch = useDispatch(); const token = useSelector((state) => state.auth.authToken); + const urls = useSelector((state) => state.urls.urls); const fetchingUrls = useCallback(() => { dispatch(fetchUrls({ token })); }, [dispatch, token]); - const urls = useSelector((state) => state.urls.urls); - let UrlsbyRecency = useMemo(sortUrls.bind(this, urls), [sortUrls, urls]); - UrlsbyRecency = UrlsbyRecency.slice(0, 5); - + const UrlsbyRecency = urls.slice(0, 5); useEffect(() => { fetchingUrls(); - }, [fetchingUrls]); - + }, + [fetchingUrls]); return (
@@ -117,6 +110,7 @@ const RecentUrlList = (props) => {
{UrlsbyRecency.map((url, index) => { return { + dispatch(setSelectedUrl({ selectedUrl: url })); return setSelected(index); }}/>; })} diff --git a/client/src/components/dashboard/RecentUrlcontainer.js b/client/src/components/dashboard/RecentUrlcontainer.js index 00deca8..571a2c4 100644 --- a/client/src/components/dashboard/RecentUrlcontainer.js +++ b/client/src/components/dashboard/RecentUrlcontainer.js @@ -8,7 +8,7 @@ import React from 'react'; import RecentUrlList from './RecentUrlList'; const useStyles = makeStyles((theme) => ({ container: { - width: '60%', + width: '90%', height: '100%', }, })); diff --git a/client/src/components/dashboard/index.js b/client/src/components/dashboard/index.js index 890508d..7866781 100644 --- a/client/src/components/dashboard/index.js +++ b/client/src/components/dashboard/index.js @@ -4,35 +4,27 @@ import { makeStyles, } from '@material-ui/core'; import RecentUrlContainer from './RecentUrlcontainer'; +import PieChart from './Graph/PieChart'; +import LineChart from './Graph/LineChart'; const useStyles = makeStyles((theme) => ({ root: { - display: 'flex', - flexDirection: 'column', - height: '100%', - width: '80%', - padding: '20px', + 'display': 'flex', + 'height': '100%', + 'width': 'min(740px, 100%)', + }, - toproot: { + leftContainer: { display: 'flex', - flexDirection: 'row', - height: '40%', + flexDirection: 'column', justifyContent: 'space-between', + flexGrow: 3, }, - bottomroot: { + rightContainer: { + padding: '10px', display: 'flex', - flexDirection: 'row', - height: '60%', - justifyContent: 'space-between', - }, - recentUrlcontainer: { - width: '30%', - textAlign: 'center', - }, - leftview: { - width: '30%', - }, - rightview: { - width: '70%', + flexDirection: 'column', + justifyContent: 'space-evenly', + flexGrow: 2, }, })); @@ -41,22 +33,13 @@ export default function Dashboard() { return (
-
-
-

My URls

-
-
-

Details

-
-
-
+
+ -
-

Total clicks in the last month

-
-
-

Recently added urls

-
+
+
+ +
); diff --git a/client/src/graphQl/queries/getUrlsQuery.js b/client/src/graphQl/queries/getUrlsQuery.js index d16b0ca..ab08fe3 100644 --- a/client/src/graphQl/queries/getUrlsQuery.js +++ b/client/src/graphQl/queries/getUrlsQuery.js @@ -16,7 +16,7 @@ const getUrlsQuery = () => { time location{ city - country + state } } } diff --git a/client/src/redux/actionTypes.js b/client/src/redux/actionTypes.js index 12f5be1..d98e987 100644 --- a/client/src/redux/actionTypes.js +++ b/client/src/redux/actionTypes.js @@ -3,3 +3,4 @@ export const FETCH_IS_DRAWER_OPEN = 'FETCH_IS_DRAWER_OPEN'; export const LOGIN = 'LOGIN'; export const FETCH_LOGIN = 'FETCH_LOGIN'; export const FETCH_URLS = 'FETCH_URLS'; +export const SET_SELECTED_URL = 'SET_SELECTED_URL'; diff --git a/client/src/redux/slices/urls.js b/client/src/redux/slices/urls.js index 924d0e4..006599a 100644 --- a/client/src/redux/slices/urls.js +++ b/client/src/redux/slices/urls.js @@ -2,7 +2,7 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import getUrlsQuery from '../../graphQl/queries/getUrlsQuery'; import axios from '../../utils/axios'; -import { FETCH_URLS } from '../actionTypes'; +import { FETCH_URLS, SET_SELECTED_URL } from '../actionTypes'; export const fetchUrls = createAsyncThunk(FETCH_URLS, async (payload) => { @@ -15,18 +15,31 @@ export const fetchUrls = createAsyncThunk(FETCH_URLS, }, }); const urls = response.data.data.getUrls.urls; + return { urls }; } catch (err) { return { urls: [] }; } }); +export const setSelectedUrl = createAsyncThunk(SET_SELECTED_URL, + async (payload) => { + const { selectedUrl } = payload; + localStorage.setItem('selectedUrlId', selectedUrl._id); + return { + selectedUrl, + }; + }); export const urlSlice = createSlice({ name: 'urls', - initialState: { urls: [] }, + initialState: { urls: [], selectedUrl: null }, extraReducers: { [fetchUrls.fulfilled]: (state, action) => ({ ...state, ...action.payload, }), + [setSelectedUrl.fulfilled]: (state, action) => ({ + ...state, + ...action.payload, + }), }, }); diff --git a/redirection-server/middlewares/redirect.js b/redirection-server/middlewares/redirect.js index e6b7052..4d5132e 100644 --- a/redirection-server/middlewares/redirect.js +++ b/redirection-server/middlewares/redirect.js @@ -38,15 +38,18 @@ const redirect = async (req, res) => { * after every redirection request */ - const ip = (req.headers['x-forwarded-for'] || '').split(',').pop().trim() || + let ip = (req.headers['x-forwarded-for'] || '').split(',').pop().trim() || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress; + if (ip === '::1') { + ip = '49.37.5.225'; + } const time = new Date().toISOString(); const apiEndpoint = BASE_URL + ip + '?access_key=' + key; const location = await axios.get(apiEndpoint); const click = new Click({ - location: { city: location.data, country: location.data.country_name }, + location: { city: location.data.city, state: location.data.region_name }, time, ip, }); diff --git a/server/graphQl/resolvers.js b/server/graphQl/resolvers.js index 3605e56..3d26c3c 100644 --- a/server/graphQl/resolvers.js +++ b/server/graphQl/resolvers.js @@ -5,6 +5,7 @@ const validator = require('../validator'); const User = require('../models/User'); const Url = require('../models/Url'); const { GraphQLDateTime } = require('graphql-iso-date'); +const { ObjectID } = require('mongodb'); const customScalarResolver = { Date: GraphQLDateTime, @@ -209,10 +210,18 @@ const getUrls = async ({ }, request) => { error.code = 407; throw error; } - const urls = await Url.find({ owner: userId }); + const urls = await Url.aggregate([{ + $match: { + owner: new ObjectID(userId), + }, + }, { + $sort: { 'createdAt': -1 }, + }], + ); // Retrieve all the urls owned by the user return { urls }; }; + module.exports = { signUp, login, shortenUrl, addDetails, getUrls, customScalarResolver, }; diff --git a/server/graphQl/schema.js b/server/graphQl/schema.js index b747fed..e686586 100644 --- a/server/graphQl/schema.js +++ b/server/graphQl/schema.js @@ -26,7 +26,7 @@ module.exports = buildSchema(` } type location{ city : String - country : String + state : String } type Click{ ip : String! From fd5ad3affbb683a1c33b44a0b17eb792122a4847 Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Mon, 2 Nov 2020 18:47:21 +0530 Subject: [PATCH 3/7] Add: Line Chart and Pie Charts --- .../components/dashboard/Graph/LineChart.js | 78 +++++++++ .../components/dashboard/Graph/PieChart.js | 161 ++++++++++++++++++ .../dashboard/RecentUrlList/index.css | 2 +- .../dashboard/RecentUrlList/index.js | 9 +- .../dashboard/RecentUrlcontainer.js | 34 +++- client/src/components/dashboard/index.js | 6 +- 6 files changed, 282 insertions(+), 8 deletions(-) create mode 100644 client/src/components/dashboard/Graph/LineChart.js create mode 100644 client/src/components/dashboard/Graph/PieChart.js diff --git a/client/src/components/dashboard/Graph/LineChart.js b/client/src/components/dashboard/Graph/LineChart.js new file mode 100644 index 0000000..dccd470 --- /dev/null +++ b/client/src/components/dashboard/Graph/LineChart.js @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import { Line } from 'react-chartjs-2'; +import { useSelector } from 'react-redux'; +/** + * Line Chart component + * This renders a line chart plotting the no of clicks made per day + */ + + +// The component to render the line chart +function LineChart() { + const [height, setHeight] = useState(window.innerHeight * 0.35); + const [width, setWidth] = useState(window.innerWidth * 0.5); + // Retreive the selected Url + const selectedUrl = useSelector((state) => state.urls.selectedUrl); + const clicks = selectedUrl ? selectedUrl.clicks : []; + + useEffect(() => { + window.onresize = () => { + setWidth(window.innerWidth * 0.5); + setHeight(window.innerHeight * 0.35); + }; + }); + + // Group the Clicks according to date + const groups = clicks.reduce((groups, click) => { + // Retrieve the Date from clicked time + const date = click.time.split('T')[0]; + if (!groups[date]) { + groups[date] = []; + } + groups[date].push(click); + return groups; + }, {}); + // Generate an array according to the groups made + const groupArrays = Object.keys(groups).map((date) => { + return { + date, + clicks: groups[date], + }; + }); + + // Data to be passed to the chart + const state = { + labels: groupArrays.map((groups) => groups.date), + datasets: [{ + label: 'Clicks', + fill: false, + lineTension: 0.5, + backgroundColor: 'rgba(75,192,192,1)', + borderColor: 'rgba(0,0,0,1)', + borderWidth: 2, + data: groupArrays.map((groups) => groups.clicks.length), + }], + }; + + return (
+ +
); +} + +export default LineChart; diff --git a/client/src/components/dashboard/Graph/PieChart.js b/client/src/components/dashboard/Graph/PieChart.js new file mode 100644 index 0000000..e31826e --- /dev/null +++ b/client/src/components/dashboard/Graph/PieChart.js @@ -0,0 +1,161 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Pie } from 'react-chartjs-2'; +import { useSelector } from 'react-redux'; + +/** + * This is the Pie Chart component + * This has two variants + * 1. This plots the no of clicks made in the last month per location on a pieChart + * 2. This plots the no of clicks made in the last month per url on a pieChart + * @usage Just import it and pass a variant prop + * whose value should be 'Clicks_by_location' for 1 else Clicks_by_urls for 2 + */ + +export default function PieChart(props) { + const { variant } = props; + + const urls = useSelector((state) => state.urls.urls); + const selectedUrl = useSelector((state) => state.urls.selectedUrl); + const clicks = selectedUrl ? selectedUrl.clicks : []; + let state = null; + let title = null; + + // Check if the + if (variant === 'Clicks_by_location') { + const groups = clicks.reduce((groups, click) => { + const location = click.location.state; + if (!groups[location]) { + groups[location] = []; + } + const date1 = new Date(); + const date2 = new Date(click.time.split('T')[0]); + const diffDays = Math.ceil((date1 - date2) / (1000 * 60 * 60 * 24)); + if (diffDays <= 31) { + groups[location].push(click); + } + return groups; + }, {}); + const groupArrays = Object.keys(groups).map((location) => { + return { + location, + clicks: groups[location], + }; + }); + state = { + labels: groupArrays.map((groups) => groups.location), + datasets: [{ + label: 'Clicks', + backgroundColor: [ + '#f95d6a', + '#ff7c43', + '#ffa600', + '#2a5c36', + '#386323', + '#4f6702', + '#6b6900', + '#8c6600', + '#b15d00', + '#d84900', + '#ff1313'], + hoverbackgroundColor: [ + '#B21F00', + '#C9DE00', + '#2FDE00', + '#00A6B4', + '#6800B4', + '#003f5c', + '#2f4b7c', + '#665191', + '#a05195', + '#d45087', + ], + data: groupArrays.map((groups) => groups.clicks.length), + }], + }; + title = 'Urls Clicked by location'; + } else if (variant === 'Clicks_by_urls') { + const groups = urls.reduce((groups, url) => { + const clicks = url.clicks ? url.clicks : []; + const title = url.title ? url.title : url.shortUrl; + for (const click of clicks) { + const date1 = new Date(); + const date2 = new Date(click.time.split('T')[0]); + const diffDays = Math.ceil((date1 - date2) / (1000 * 60 * 60 * 24)); + if (!groups[title]) { + groups[title] = []; + } + if (diffDays <= 31) { + groups[title].push(click); + } + } + return groups; + }, {}); + const groupArrays = Object.keys(groups).map((title) => { + return { + title, + clicks: groups[title], + }; + }); + state = { + labels: groupArrays.map((groups) => groups.title), + datasets: [{ + label: 'Clicks', + backgroundColor: [ + '#f95d6a', + '#ff7c43', + '#ffa600', + '#2a5c36', + '#386323', + '#4f6702', + '#6b6900', + '#8c6600', + '#b15d00', + '#d84900', + '#ff1313'], + hoverbackgroundColor: [ + '#B21F00', + '#C9DE00', + '#2FDE00', + '#00A6B4', + '#6800B4', + '#003f5c', + '#2f4b7c', + '#665191', + '#a05195', + '#d45087', + ], + data: groupArrays.map((groups) => groups.clicks.length), + }], + }; + title = 'Urls Clicked Last Month'; + } + return ( +
+ +
+ ); +} + +PieChart.propTypes = { + variant: PropTypes.string, +}; diff --git a/client/src/components/dashboard/RecentUrlList/index.css b/client/src/components/dashboard/RecentUrlList/index.css index 2c1d90c..ad2de61 100644 --- a/client/src/components/dashboard/RecentUrlList/index.css +++ b/client/src/components/dashboard/RecentUrlList/index.css @@ -3,7 +3,7 @@ height: '12%'; } .active{ - overflow: 'visible'; + display: flex; width: '100%'; background-color: slategray; color: white; diff --git a/client/src/components/dashboard/RecentUrlList/index.js b/client/src/components/dashboard/RecentUrlList/index.js index 4b125db..c258816 100644 --- a/client/src/components/dashboard/RecentUrlList/index.js +++ b/client/src/components/dashboard/RecentUrlList/index.js @@ -1,6 +1,5 @@ -/* eslint-disable no-invalid-this */ -/* eslint-disable react/prop-types */ import { makeStyles } from '@material-ui/core'; +import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchUrls, setSelectedUrl } from '../../../redux/slices/urls'; @@ -117,5 +116,9 @@ const RecentUrlList = (props) => {
); }; - +UrlItem.propTypes = { + url: PropTypes.object, + onClick: PropTypes.func, + active: PropTypes.bool, +}; export default RecentUrlList; diff --git a/client/src/components/dashboard/RecentUrlcontainer.js b/client/src/components/dashboard/RecentUrlcontainer.js index 571a2c4..0ccd018 100644 --- a/client/src/components/dashboard/RecentUrlcontainer.js +++ b/client/src/components/dashboard/RecentUrlcontainer.js @@ -1,10 +1,14 @@ import { + Button, Card, CardActions, CardHeader, makeStyles, + useMediaQuery, + useTheme, } from '@material-ui/core'; import React from 'react'; +import { useHistory } from 'react-router-dom'; import RecentUrlList from './RecentUrlList'; const useStyles = makeStyles((theme) => ({ container: { @@ -14,7 +18,18 @@ const useStyles = makeStyles((theme) => ({ })); const RecentUrlContainer = (props) => { + const theme = useTheme(); const classes = useStyles(); + const history = useHistory(); + const mediaMinSm = useMediaQuery(theme.breakpoints.up('sm')); + + const myUrlsHandler = () => { + history.push({ + pathname: '/MyUrls', + }); + }; + + return (
{ display: 'flex', flexDirection: 'column', }}> - +
+ +
+
+ +
); diff --git a/client/src/components/dashboard/index.js b/client/src/components/dashboard/index.js index 7866781..861c879 100644 --- a/client/src/components/dashboard/index.js +++ b/client/src/components/dashboard/index.js @@ -16,7 +16,7 @@ const useStyles = makeStyles((theme) => ({ leftContainer: { display: 'flex', flexDirection: 'column', - justifyContent: 'space-between', + justifyContent: 'space-evenly', flexGrow: 3, }, rightContainer: { @@ -38,8 +38,8 @@ export default function Dashboard() {
- - + +
); From db58d0592203e2351f1fd62c01abe6ce0d76119d Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Mon, 2 Nov 2020 20:43:04 +0530 Subject: [PATCH 4/7] fixed trivial issues --- .../components/dashboard/Graph/LineChart.js | 1 - .../dashboard/RecentUrlcontainer.js | 34 ++++---------- .../{RecentUrlList => UrlList}/index.css | 0 .../{RecentUrlList => UrlList}/index.js | 47 ++++++++++++++----- client/src/components/dashboard/index.js | 10 ++-- client/src/redux/slices/urls.js | 2 + server/index.js | 1 - 7 files changed, 54 insertions(+), 41 deletions(-) rename client/src/components/dashboard/{RecentUrlList => UrlList}/index.css (100%) rename client/src/components/dashboard/{RecentUrlList => UrlList}/index.js (75%) diff --git a/client/src/components/dashboard/Graph/LineChart.js b/client/src/components/dashboard/Graph/LineChart.js index dccd470..4f3d2ba 100644 --- a/client/src/components/dashboard/Graph/LineChart.js +++ b/client/src/components/dashboard/Graph/LineChart.js @@ -6,7 +6,6 @@ import { useSelector } from 'react-redux'; * This renders a line chart plotting the no of clicks made per day */ - // The component to render the line chart function LineChart() { const [height, setHeight] = useState(window.innerHeight * 0.35); diff --git a/client/src/components/dashboard/RecentUrlcontainer.js b/client/src/components/dashboard/RecentUrlcontainer.js index 0ccd018..fb38123 100644 --- a/client/src/components/dashboard/RecentUrlcontainer.js +++ b/client/src/components/dashboard/RecentUrlcontainer.js @@ -1,5 +1,4 @@ import { - Button, Card, CardActions, CardHeader, @@ -8,8 +7,9 @@ import { useTheme, } from '@material-ui/core'; import React from 'react'; -import { useHistory } from 'react-router-dom'; -import RecentUrlList from './RecentUrlList'; +import UrlList from './UrlList'; + +// Create styles for the component const useStyles = makeStyles((theme) => ({ container: { width: '90%', @@ -17,26 +17,19 @@ const useStyles = makeStyles((theme) => ({ }, })); -const RecentUrlContainer = (props) => { +// Url Container component +const UrlContainer = (props) => { const theme = useTheme(); const classes = useStyles(); - const history = useHistory(); const mediaMinSm = useMediaQuery(theme.breakpoints.up('sm')); - const myUrlsHandler = () => { - history.push({ - pathname: '/MyUrls', - }); - }; - - return (
{
{ paddingBottom: mediaMinSm ? '16px' : '6px', width: '100%', }}> - -
-
- +
); }; -export default RecentUrlContainer; +export default UrlContainer; diff --git a/client/src/components/dashboard/RecentUrlList/index.css b/client/src/components/dashboard/UrlList/index.css similarity index 100% rename from client/src/components/dashboard/RecentUrlList/index.css rename to client/src/components/dashboard/UrlList/index.css diff --git a/client/src/components/dashboard/RecentUrlList/index.js b/client/src/components/dashboard/UrlList/index.js similarity index 75% rename from client/src/components/dashboard/RecentUrlList/index.js rename to client/src/components/dashboard/UrlList/index.js index c258816..ad31a67 100644 --- a/client/src/components/dashboard/RecentUrlList/index.js +++ b/client/src/components/dashboard/UrlList/index.js @@ -4,6 +4,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { fetchUrls, setSelectedUrl } from '../../../redux/slices/urls'; import './index.css'; + +// Create the styles for the components const useStyles = makeStyles((theme) => ({ list: { display: 'flex', @@ -12,9 +14,17 @@ const useStyles = makeStyles((theme) => ({ height: '100%', }, })); + +// Function to style and render an url item const UrlItem = ({ url, onClick, active }) => { + // Extract and store the date of creation const createdAt = new Date(url.createdAt).toDateString().substring(4); + /** Check if current url has been clicked or not + * if yes then then extract its length + * else store 0 + */ const clicksLength = url.clicks ? url.clicks.length : 0; + return (
{
); }; -const RecentUrlList = (props) => { + +const UrlList = (props) => { const [selected, setSelected] = useState(); const classes = useStyles(); const dispatch = useDispatch(); const token = useSelector((state) => state.auth.authToken); const urls = useSelector((state) => state.urls.urls); + + /** Function to dispatch the fetch Urls reducer + * which updates the url list current component is using if neccessary + */ const fetchingUrls = useCallback(() => { dispatch(fetchUrls({ token })); }, [dispatch, token]); - const UrlsbyRecency = urls.slice(0, 5); + // Slice out the five most recent Urls + const UrlsbyRecency = urls; + useEffect(() => { fetchingUrls(); }, [fetchingUrls]); + return (
@@ -79,9 +97,7 @@ const RecentUrlList = (props) => { alignItems: 'center', fontWeight: 'bold', textAlign: 'end', - borderBottom: 'solid', - borderBottomWidth: '0.25px', - borderBottomColor: 'grey', + borderBottom: '0.25px solid grey', }}>
{

Clicks

- {UrlsbyRecency.map((url, index) => { - return { - dispatch(setSelectedUrl({ selectedUrl: url })); - return setSelected(index); - }}/>; - })} +
+ {UrlsbyRecency.map((url, index) => { + return { + dispatch(setSelectedUrl({ selectedUrl: url })); + return setSelected(index); + }}/>; + })} +
); }; + UrlItem.propTypes = { url: PropTypes.object, onClick: PropTypes.func, active: PropTypes.bool, }; -export default RecentUrlList; + +export default UrlList; diff --git a/client/src/components/dashboard/index.js b/client/src/components/dashboard/index.js index 861c879..7616f15 100644 --- a/client/src/components/dashboard/index.js +++ b/client/src/components/dashboard/index.js @@ -3,9 +3,11 @@ import { CssBaseline, makeStyles, } from '@material-ui/core'; -import RecentUrlContainer from './RecentUrlcontainer'; +import UrlContainer from './RecentUrlcontainer'; import PieChart from './Graph/PieChart'; import LineChart from './Graph/LineChart'; + +// Create styles for this component const useStyles = makeStyles((theme) => ({ root: { 'display': 'flex', @@ -23,11 +25,13 @@ const useStyles = makeStyles((theme) => ({ padding: '10px', display: 'flex', flexDirection: 'column', - justifyContent: 'space-evenly', + justifyContent: 'center', + alignItems: 'center', flexGrow: 2, }, })); +// Function to render the Dashboard component export default function Dashboard() { const classes = useStyles(); return ( @@ -35,7 +39,7 @@ export default function Dashboard() {
- +
diff --git a/client/src/redux/slices/urls.js b/client/src/redux/slices/urls.js index 006599a..6f2f228 100644 --- a/client/src/redux/slices/urls.js +++ b/client/src/redux/slices/urls.js @@ -21,6 +21,7 @@ export const fetchUrls = createAsyncThunk(FETCH_URLS, return { urls: [] }; } }); + export const setSelectedUrl = createAsyncThunk(SET_SELECTED_URL, async (payload) => { const { selectedUrl } = payload; @@ -29,6 +30,7 @@ export const setSelectedUrl = createAsyncThunk(SET_SELECTED_URL, selectedUrl, }; }); + export const urlSlice = createSlice({ name: 'urls', initialState: { urls: [], selectedUrl: null }, diff --git a/server/index.js b/server/index.js index d0164fa..e3e115d 100644 --- a/server/index.js +++ b/server/index.js @@ -63,7 +63,6 @@ mongoose app.listen(PORT, () => console.log(`Listening on port ${PORT}. /graphql for GraphiQl`)); }) .catch((err) => { - console.log(err); // If not connected, exit the process // eslint-disable-next-line no-console console.log('Error while connecting to mongodb: ', err); From 947b9ccb217b483e75a3aad714b94989ab8f7673 Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Fri, 6 Nov 2020 14:01:07 +0530 Subject: [PATCH 5/7] fixed some ui issues and upgraded to highcharts --- client/package-lock.json | 10 ++ client/package.json | 2 + .../components/dashboard/Graph/LineChart.js | 85 ++++----- .../components/dashboard/Graph/PieChart.js | 165 +++++++++--------- .../dashboard/RecentUrlcontainer.js | 10 +- .../src/components/dashboard/UrlList/index.js | 4 +- client/src/components/dashboard/index.js | 29 +-- 7 files changed, 161 insertions(+), 144 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 402d156..d50d385 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -6426,6 +6426,16 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "highcharts": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-8.2.2.tgz", + "integrity": "sha512-F63TXO7RxsvTcpO/KOubQZWualYpCMyCTuKtoWbt7KCsfQ3Kl7Fr6HEyyJdjkYl+XlnmnKlSRi9d3HjLK9Q0wg==" + }, + "highcharts-react-official": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.0.0.tgz", + "integrity": "sha512-VefJgDY2hkT9gfppsQGrRF2g5u8d9dtfHGcx2/xqiP+PkZXCqalw9xOeKVCRvJKTOh0coiDFwvVjOvB7KaGl4A==" + }, "history": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", diff --git a/client/package.json b/client/package.json index f94420b..421d945 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,8 @@ "axios": "^0.20.0", "chart.js": "^2.9.4", "graphql": "^15.3.0", + "highcharts": "^8.2.2", + "highcharts-react-official": "^3.0.0", "jwt-decode": "^3.0.0", "prop-types": "^15.7.2", "react": "^16.13.1", diff --git a/client/src/components/dashboard/Graph/LineChart.js b/client/src/components/dashboard/Graph/LineChart.js index 4f3d2ba..6de27c9 100644 --- a/client/src/components/dashboard/Graph/LineChart.js +++ b/client/src/components/dashboard/Graph/LineChart.js @@ -1,5 +1,6 @@ -import React, { useEffect, useState } from 'react'; -import { Line } from 'react-chartjs-2'; +import React from 'react'; +import Highcharts from 'highcharts/highstock'; +import HighchartsReact from 'highcharts-react-official'; import { useSelector } from 'react-redux'; /** * Line Chart component @@ -8,19 +9,10 @@ import { useSelector } from 'react-redux'; // The component to render the line chart function LineChart() { - const [height, setHeight] = useState(window.innerHeight * 0.35); - const [width, setWidth] = useState(window.innerWidth * 0.5); - // Retreive the selected Url + // // Retreive the selected Url const selectedUrl = useSelector((state) => state.urls.selectedUrl); const clicks = selectedUrl ? selectedUrl.clicks : []; - useEffect(() => { - window.onresize = () => { - setWidth(window.innerWidth * 0.5); - setHeight(window.innerHeight * 0.35); - }; - }); - // Group the Clicks according to date const groups = clicks.reduce((groups, click) => { // Retrieve the Date from clicked time @@ -40,38 +32,51 @@ function LineChart() { }); // Data to be passed to the chart - const state = { - labels: groupArrays.map((groups) => groups.date), - datasets: [{ - label: 'Clicks', - fill: false, - lineTension: 0.5, - backgroundColor: 'rgba(75,192,192,1)', - borderColor: 'rgba(0,0,0,1)', - borderWidth: 2, + const options = { + chart: { + reflow: 'true', + BackgroundColor: '#fbfbfb', + plotBorderWidth: null, + plotShadow: false, + type: 'line', + }, + title: { + text: 'Clicks by Date ', + align: 'left', + }, + xAxis: { + title: { + text: 'Date', + }, + categories: groupArrays.map((groups) => groups.date), + }, + yAxis: { + title: { + text: 'Clicks', + }, + }, + series: [{ + name: 'Clicks', data: groupArrays.map((groups) => groups.clicks.length), }], }; - return (
- -
); + return ( +
+
+ + +
+
); } export default LineChart; diff --git a/client/src/components/dashboard/Graph/PieChart.js b/client/src/components/dashboard/Graph/PieChart.js index e31826e..db51e59 100644 --- a/client/src/components/dashboard/Graph/PieChart.js +++ b/client/src/components/dashboard/Graph/PieChart.js @@ -1,6 +1,7 @@ +import HighchartsReact from 'highcharts-react-official'; +import Highcharts from 'highcharts/highstock'; import PropTypes from 'prop-types'; import React from 'react'; -import { Pie } from 'react-chartjs-2'; import { useSelector } from 'react-redux'; /** @@ -18,8 +19,7 @@ export default function PieChart(props) { const urls = useSelector((state) => state.urls.urls); const selectedUrl = useSelector((state) => state.urls.selectedUrl); const clicks = selectedUrl ? selectedUrl.clicks : []; - let state = null; - let title = null; + let options = null; // Check if the if (variant === 'Clicks_by_location') { @@ -38,42 +38,43 @@ export default function PieChart(props) { }, {}); const groupArrays = Object.keys(groups).map((location) => { return { - location, - clicks: groups[location], + name: location, + y: groups[location].length, }; }); - state = { - labels: groupArrays.map((groups) => groups.location), - datasets: [{ - label: 'Clicks', - backgroundColor: [ - '#f95d6a', - '#ff7c43', - '#ffa600', - '#2a5c36', - '#386323', - '#4f6702', - '#6b6900', - '#8c6600', - '#b15d00', - '#d84900', - '#ff1313'], - hoverbackgroundColor: [ - '#B21F00', - '#C9DE00', - '#2FDE00', - '#00A6B4', - '#6800B4', - '#003f5c', - '#2f4b7c', - '#665191', - '#a05195', - '#d45087', - ], - data: groupArrays.map((groups) => groups.clicks.length), + options = { + chart: { + plotBackgroundColor: '#fbfbfb', + plotBorderWidth: null, + plotShadow: false, + type: 'pie', + }, + legend: { + layout: 'vertical', + align: 'right', + verticalAlign: 'middle', + }, + title: { + text: 'Clicks by Location', + align: 'left', + }, + tooltip: { + pointFormat: '{series.name}: {point.percentage:.1f}%', + }, + plotOptions: { + pie: { + dataLabels: { + enabled: true, + format: '{point.name}:{point.percentage:.1f} %', + }, + }, + }, + series: [{ + name: 'Clicks', + colorByPoint: true, + data: groupArrays, }], }; - title = 'Urls Clicked by location'; } else if (variant === 'Clicks_by_urls') { const groups = urls.reduce((groups, url) => { const clicks = url.clicks ? url.clicks : []; @@ -93,65 +94,59 @@ export default function PieChart(props) { }, {}); const groupArrays = Object.keys(groups).map((title) => { return { - title, - clicks: groups[title], + name: title, + y: groups[title].length, }; }); - state = { - labels: groupArrays.map((groups) => groups.title), - datasets: [{ - label: 'Clicks', - backgroundColor: [ - '#f95d6a', - '#ff7c43', - '#ffa600', - '#2a5c36', - '#386323', - '#4f6702', - '#6b6900', - '#8c6600', - '#b15d00', - '#d84900', - '#ff1313'], - hoverbackgroundColor: [ - '#B21F00', - '#C9DE00', - '#2FDE00', - '#00A6B4', - '#6800B4', - '#003f5c', - '#2f4b7c', - '#665191', - '#a05195', - '#d45087', - ], - data: groupArrays.map((groups) => groups.clicks.length), + options = { + chart: { + plotBackgroundColor: '#fbfbfb', + plotBorderWidth: null, + plotShadow: false, + type: 'pie', + }, + legend: { + layout: 'vertical', + align: 'right', + verticalAlign: 'middle', + }, + title: { + text: 'Clicks last month', + align: 'left', + }, + tooltip: { + pointFormat: '{series.name}: {point.percentage:.1f}%', + }, + plotOptions: { + pie: { + dataLabels: { + enabled: true, + format: '{point.name}:{point.percentage:.1f} %', + }, + }, + }, + series: [{ + name: 'Clicks', + colorByPoint: true, + data: groupArrays, }], }; - title = 'Urls Clicked Last Month'; } return (
- +
+ +
); } diff --git a/client/src/components/dashboard/RecentUrlcontainer.js b/client/src/components/dashboard/RecentUrlcontainer.js index fb38123..300513b 100644 --- a/client/src/components/dashboard/RecentUrlcontainer.js +++ b/client/src/components/dashboard/RecentUrlcontainer.js @@ -12,8 +12,9 @@ import UrlList from './UrlList'; // Create styles for the component const useStyles = makeStyles((theme) => ({ container: { - width: '90%', - height: '100%', + display: 'flex', + flexGrow: 1, + flexBasis: '50%', }, })); @@ -26,13 +27,14 @@ const UrlContainer = (props) => { return (
diff --git a/client/src/components/dashboard/UrlList/index.js b/client/src/components/dashboard/UrlList/index.js index ad31a67..b87c72a 100644 --- a/client/src/components/dashboard/UrlList/index.js +++ b/client/src/components/dashboard/UrlList/index.js @@ -90,7 +90,7 @@ const UrlList = (props) => { paddingTop: '0px', paddingLeft: '2px', width: '100%', - height: '12%', + height: '8%', display: 'flex', flexDirection: 'row', justifyContent: 'space-between', @@ -124,7 +124,7 @@ const UrlList = (props) => {
{UrlsbyRecency.map((url, index) => { diff --git a/client/src/components/dashboard/index.js b/client/src/components/dashboard/index.js index 7616f15..3f3d215 100644 --- a/client/src/components/dashboard/index.js +++ b/client/src/components/dashboard/index.js @@ -10,22 +10,23 @@ import LineChart from './Graph/LineChart'; // Create styles for this component const useStyles = makeStyles((theme) => ({ root: { + margin: '10px', + }, + container: { 'display': 'flex', 'height': '100%', - 'width': 'min(740px, 100%)', - + 'width': '100%', }, leftContainer: { display: 'flex', flexDirection: 'column', - justifyContent: 'space-evenly', + margin: '10px', flexGrow: 3, }, rightContainer: { - padding: '10px', display: 'flex', flexDirection: 'column', - justifyContent: 'center', + padding: '10px', alignItems: 'center', flexGrow: 2, }, @@ -37,14 +38,16 @@ export default function Dashboard() { return (
-
- - -
-
- - -
+
+
+ + +
+
+ + +
+
); }; From a1732531fb378615f0094eeecd76b67d2019adc4 Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Fri, 6 Nov 2020 23:54:07 +0530 Subject: [PATCH 6/7] fixed minor issues --- client/src/components/dashboard/Graph/PieChart.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/dashboard/Graph/PieChart.js b/client/src/components/dashboard/Graph/PieChart.js index db51e59..885092e 100644 --- a/client/src/components/dashboard/Graph/PieChart.js +++ b/client/src/components/dashboard/Graph/PieChart.js @@ -21,10 +21,10 @@ export default function PieChart(props) { const clicks = selectedUrl ? selectedUrl.clicks : []; let options = null; - // Check if the + // Check if the variant is one needing location if (variant === 'Clicks_by_location') { const groups = clicks.reduce((groups, click) => { - const location = click.location.state; + const location = click.location.city + ',' + click.location.state; if (!groups[location]) { groups[location] = []; } From 70770c645032e4ac8548265a48be7e5979fa0003 Mon Sep 17 00:00:00 2001 From: MD FAIZ ANSARI Date: Sun, 8 Nov 2020 00:27:13 +0530 Subject: [PATCH 7/7] fixed minor issues --- client/package-lock.json | 40 ------------------- client/package.json | 2 - client/src/App.js | 2 - .../components/dashboard/Graph/LineChart.js | 3 +- .../components/dashboard/Graph/PieChart.js | 7 ++-- .../src/components/dashboard/UrlList/index.js | 22 +++++++--- client/src/components/dashboard/index.js | 4 +- client/src/redux/slices/urls.js | 4 +- server/graphQl/resolvers.js | 3 +- 9 files changed, 28 insertions(+), 59 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index d50d385..82bf3b1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -3595,32 +3595,6 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, - "chart.js": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", - "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", - "requires": { - "chartjs-color": "^2.1.0", - "moment": "^2.10.2" - } - }, - "chartjs-color": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", - "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", - "requires": { - "chartjs-color-string": "^0.6.0", - "color-convert": "^1.9.3" - } - }, - "chartjs-color-string": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", - "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", - "requires": { - "color-name": "^1.0.0" - } - }, "chokidar": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", @@ -8591,11 +8565,6 @@ "minimist": "^1.2.5" } }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -10703,15 +10672,6 @@ "whatwg-fetch": "^3.0.0" } }, - "react-chartjs-2": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.10.0.tgz", - "integrity": "sha512-1MjWEkUn8LLFf6GVyYUOrruJTW3yVU5hlEJOwGj3MiokuC+jH/BahjWVGAMonbe9UYbEIUbd2Rn36iVlC0Hb7w==", - "requires": { - "lodash": "^4.17.19", - "prop-types": "^15.7.2" - } - }, "react-dev-utils": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", diff --git a/client/package.json b/client/package.json index 421d945..23d6297 100644 --- a/client/package.json +++ b/client/package.json @@ -11,14 +11,12 @@ "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "axios": "^0.20.0", - "chart.js": "^2.9.4", "graphql": "^15.3.0", "highcharts": "^8.2.2", "highcharts-react-official": "^3.0.0", "jwt-decode": "^3.0.0", "prop-types": "^15.7.2", "react": "^16.13.1", - "react-chartjs-2": "^2.10.0", "react-dom": "^16.13.1", "react-redux": "^7.2.1", "react-router-dom": "^5.2.0", diff --git a/client/src/App.js b/client/src/App.js index 2ca2448..6ee7545 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -13,7 +13,6 @@ import { fetchLogin } from './redux/slices/auth'; import './App.css'; import Dashboard from './components/dashboard'; -import { fetchUrls } from './redux/slices/urls'; const useStyles = makeStyles((theme) => ({ root: { @@ -46,7 +45,6 @@ function App() { useEffect(() => { dispatch(fetchIsDrawerOpen()); dispatch(fetchLogin()); - dispatch(fetchUrls({ token: token })); }, [dispatch, token]); return ( diff --git a/client/src/components/dashboard/Graph/LineChart.js b/client/src/components/dashboard/Graph/LineChart.js index 6de27c9..b21dbf4 100644 --- a/client/src/components/dashboard/Graph/LineChart.js +++ b/client/src/components/dashboard/Graph/LineChart.js @@ -2,6 +2,7 @@ import React from 'react'; import Highcharts from 'highcharts/highstock'; import HighchartsReact from 'highcharts-react-official'; import { useSelector } from 'react-redux'; + /** * Line Chart component * This renders a line chart plotting the no of clicks made per day @@ -9,7 +10,7 @@ import { useSelector } from 'react-redux'; // The component to render the line chart function LineChart() { - // // Retreive the selected Url + // Retreive the selected Url const selectedUrl = useSelector((state) => state.urls.selectedUrl); const clicks = selectedUrl ? selectedUrl.clicks : []; diff --git a/client/src/components/dashboard/Graph/PieChart.js b/client/src/components/dashboard/Graph/PieChart.js index 885092e..7d5c2f3 100644 --- a/client/src/components/dashboard/Graph/PieChart.js +++ b/client/src/components/dashboard/Graph/PieChart.js @@ -10,7 +10,8 @@ import { useSelector } from 'react-redux'; * 1. This plots the no of clicks made in the last month per location on a pieChart * 2. This plots the no of clicks made in the last month per url on a pieChart * @usage Just import it and pass a variant prop - * whose value should be 'Clicks_by_location' for 1 else Clicks_by_urls for 2 + * whose value should be `CLICKS_BY_LOCATION` for 1 else `CLICKS_BY_URLS` for 2 + * @example */ export default function PieChart(props) { @@ -22,7 +23,7 @@ export default function PieChart(props) { let options = null; // Check if the variant is one needing location - if (variant === 'Clicks_by_location') { + if (variant === 'CLICKS_BY_LOCATION') { const groups = clicks.reduce((groups, click) => { const location = click.location.city + ',' + click.location.state; if (!groups[location]) { @@ -75,7 +76,7 @@ export default function PieChart(props) { data: groupArrays, }], }; - } else if (variant === 'Clicks_by_urls') { + } else if (variant === 'CLICKS_BY_URLS') { const groups = urls.reduce((groups, url) => { const clicks = url.clicks ? url.clicks : []; const title = url.title ? url.title : url.shortUrl; diff --git a/client/src/components/dashboard/UrlList/index.js b/client/src/components/dashboard/UrlList/index.js index b87c72a..0aeecf8 100644 --- a/client/src/components/dashboard/UrlList/index.js +++ b/client/src/components/dashboard/UrlList/index.js @@ -2,6 +2,7 @@ import { makeStyles } from '@material-ui/core'; import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { setIsSnackbarOpen } from '../../../redux/slices/snackbar'; import { fetchUrls, setSelectedUrl } from '../../../redux/slices/urls'; import './index.css'; @@ -29,9 +30,7 @@ const UrlItem = ({ url, onClick, active }) => {
{ const dispatch = useDispatch(); const token = useSelector((state) => state.auth.authToken); const urls = useSelector((state) => state.urls.urls); + const error = useSelector((state) => state.urls.error); /** Function to dispatch the fetch Urls reducer * which updates the url list current component is using if neccessary */ const fetchingUrls = useCallback(() => { - dispatch(fetchUrls({ token })); + // fetch the urls only if user is logged in + if (token) { + dispatch(fetchUrls({ token })); + } }, [dispatch, token]); // Slice out the five most recent Urls - const UrlsbyRecency = urls; useEffect(() => { fetchingUrls(); }, [fetchingUrls]); + // If there is some error in fetching urls then notify the user accordingly + if (error) { + dispatch(setIsSnackbarOpen({ + status: 'open', + message: 'Some Unknown Error occured in fetching urls, Please Refresh', + severity: 'error', + })); + } return (
@@ -127,7 +137,7 @@ const UrlList = (props) => { height: '80%', overflowY: 'scroll', }}> - {UrlsbyRecency.map((url, index) => { + {urls.map((url, index) => { return { dispatch(setSelectedUrl({ selectedUrl: url })); return setSelected(index); diff --git a/client/src/components/dashboard/index.js b/client/src/components/dashboard/index.js index 3f3d215..1353ba5 100644 --- a/client/src/components/dashboard/index.js +++ b/client/src/components/dashboard/index.js @@ -44,8 +44,8 @@ export default function Dashboard() {
- - + +
diff --git a/client/src/redux/slices/urls.js b/client/src/redux/slices/urls.js index 6f2f228..9a23b0c 100644 --- a/client/src/redux/slices/urls.js +++ b/client/src/redux/slices/urls.js @@ -18,7 +18,7 @@ export const fetchUrls = createAsyncThunk(FETCH_URLS, return { urls }; } catch (err) { - return { urls: [] }; + return { error: true }; } }); @@ -33,7 +33,7 @@ export const setSelectedUrl = createAsyncThunk(SET_SELECTED_URL, export const urlSlice = createSlice({ name: 'urls', - initialState: { urls: [], selectedUrl: null }, + initialState: { urls: [], selectedUrl: null, error: false }, extraReducers: { [fetchUrls.fulfilled]: (state, action) => ({ ...state, diff --git a/server/graphQl/resolvers.js b/server/graphQl/resolvers.js index 3d26c3c..9c2e6b0 100644 --- a/server/graphQl/resolvers.js +++ b/server/graphQl/resolvers.js @@ -205,9 +205,10 @@ const addDetails = async ({ title, description, shortUrl, updatedShortUrl }, req const getUrls = async ({ }, request) => { // Get the user id from the request const { userId } = request; + // If user is not logged in then ask him to login first if (!userId) { const error = new Error('Please Login'); - error.code = 407; + error.code = 401; throw error; } const urls = await Url.aggregate([{