From 57a6e0261864f9d59546f7784191aca754f141f8 Mon Sep 17 00:00:00 2001 From: Aashish <83752052+120EE0692@users.noreply.github.com> Date: Sun, 15 Oct 2023 18:20:18 +0530 Subject: [PATCH] feat: onboarding (#475) * chore: user onboarding flow (#426) * feat: redirect to home on skip onboarding * chore: go to interested topic page * feat(graphql): add add user store path mutation * feat(graphql): add user interested topic mutation * feat(graphql): add user news letter subscription mutation * feat: integrate mutation to selected topics * chore: change auth state * feat: integrate newsletter subscricption * chore:change user flow * chore: change to user id * chore: add category number in interested topics * chore: change data type * feat: add update user profile picture * fix: user profile image path and mid * feat: add get firebase user * chore: change verify email flow * feat: webmail authentication userflow (#440) * feat: addNITRMail mutation * feat: imagekit config file * chore: update onboarding placeholders * fix: standardise user mutations * fix: add null check for firebaseToken * feat: sign in and sign up auth user flows * feat: implement sign in and signup auth user flows * chore: rectify imports for used and unused * feat: handle migrated accounts * fix(migrated-user): add missing field * fix: uncomment useRouter * feat: user onboarding experience (#441) * feat: add loading fallback * feat: check already registered nitr mail * fix: restore deleted imports * feat: add default profile picture * refactor: auth for server and client-side with middleware checks (#447) * feat: create auth middleware for article pages * chore(icons): use precise imports to reduce build * fix: use defaultValue instead of selected * fix: no div inside p * fix: missing key prop in home * chore: configure dev and prod for debugger * refactor: apollo context provider * refactor: apollo client for server side queries * chore: update env sample for server key * fix(middleware): retrieve correct cookies * refactor: consume client side graphClient context * refactor: merge auth context and state as provider * refactor: remove redundant files * fix(import): wrong path for checkNITRMail * fix(auth-context-provider): typo in graphClient * fix(auth-context): typo in variable name * fix(auth-context): send email link prod switch * feat: add sign in button (#425) * feat: add sign in button * fix * fixed * feat: add signIn button in mobile view * feat: add signin and signout feature (#461) * feat: add sign in button * fix * fixed * feat: add signIn button in mobile view * feat: add user avatar * feat: add signin signout --------- Co-authored-by: anish Co-authored-by: G.Anish Kumar Patro <105335549+anish-patro@users.noreply.github.com> * fix: missing pagination component (#469) * chore: rename pagination to _pagination * chore: rename _pagination to Pagination * fix: gsi call after script load (#472) * chore: remove gsi script (#473) * feat: rebase with main (#474) * chore: update CC info 2023 (#451) * feat(home): increase number of displayed articles on the home page (#453) chore(home): increase display articles * chore: delete repo docs (#455) * chore: delete repo docs * chore: format html templete * fix(home): remove duplicate article in home page (#456) * fix(photostory): optimise the state update in photostory (#444) * fix(photostory): optimise the state update * fix(photostory): update right side image in carousel * chore: update sac info page (#467) * fix: search bar (#460) * fix: search bar * fix: serach bar * fix: search bar --------- Co-authored-by: Anish Sarawgi <98693953+Anish-Sarawgi@users.noreply.github.com> Co-authored-by: Dibendu Sahani <112952404+dibendusahani@users.noreply.github.com> Co-authored-by: SUNNY KUMAR <123469525+SUNNYKUMAR1232@users.noreply.github.com> --------- Co-authored-by: Rutaj Dash <33367546+rutajdash@users.noreply.github.com> Co-authored-by: Ashish Padhy <100484401+Shurtu-gal@users.noreply.github.com> Co-authored-by: G.Anish Kumar Patro <105335549+anish-patro@users.noreply.github.com> Co-authored-by: anish Co-authored-by: Anish Sarawgi <98693953+Anish-Sarawgi@users.noreply.github.com> Co-authored-by: Dibendu Sahani <112952404+dibendusahani@users.noreply.github.com> Co-authored-by: SUNNY KUMAR <123469525+SUNNYKUMAR1232@users.noreply.github.com> --- .vscode/launch.json | 15 +- client/env/.env.sample | 1 + client/src/assets/placeholder/onboarding.js | 41 ++- .../admin/Sidebar/menulist/NavCollapse.jsx | 8 +- .../admin/Sidebar/menulist/NavItem.jsx | 2 +- client/src/components/homepage/SocialMedia.js | 3 +- .../src/components/marginals/DesktopNavbar.js | 90 ++++-- client/src/components/marginals/Footer.js | 6 +- client/src/components/marginals/TopBar.js | 73 ++++- .../src/components/onboarding/Pagination.js | 1 - .../onboarding/stages/NewsletterSignup.js | 69 ++--- .../onboarding/stages/SelectTopics.js | 87 ++++-- .../onboarding/stages/VerifyEmail.js | 243 ++++++++++----- .../components/onboarding/stages/Welcome.js | 96 ++++-- .../shared/button/ScrollToTopButton.js | 3 +- client/src/components/widgets/HomeCarousel.js | 3 +- client/src/components/widgets/PostHolder.js | 2 +- client/src/components/widgets/SocietyCards.js | 2 +- client/src/components/widgets/Squiggles.js | 4 +- client/src/components/widgets/UserAvatar.js | 68 ++++ client/src/config/ApolloClient.js | 84 ----- client/src/config/Root.js | 19 -- client/src/config/imagekit.js | 9 + client/src/context/ApolloContextProvider.js | 130 ++++++++ client/src/context/AuthContextProvider.js | 290 ++++++++++++++++++ ...Context.jsx => SidebarContextProvider.jsx} | 0 client/src/context/auth/AuthContext.js | 5 - client/src/context/auth/AuthState.js | 116 ------- .../src/graphql/mutations/user/addNITRMail.js | 15 + .../mutations/user/newsLetterSubscription.js | 12 + .../graphql/mutations/user/registerUser.js | 1 + .../user/updateUserProfilePicture.js | 26 ++ .../mutations/user/updateUserTopics.js | 12 + .../src/graphql/queries/user/checkNITRMail.js | 10 + .../queries/user/getFirebaseUserByEmail.js | 15 + client/src/hooks/useAutoComplete.js | 7 +- client/src/middleware.js | 55 ++++ client/src/pages/404.jsx | 2 +- client/src/pages/500.jsx | 2 +- .../src/pages/[category]/[...subCategory].jsx | 16 +- client/src/pages/[category]/index.jsx | 6 +- client/src/pages/_app.jsx | 20 +- .../src/pages/archive/[...yearAndMonth].jsx | 6 +- client/src/pages/article/[...article].jsx | 8 +- client/src/pages/comingSoon.jsx | 2 +- .../pages/expressions/[...subCategory].jsx | 8 +- client/src/pages/expressions/index.jsx | 12 +- client/src/pages/guide.jsx | 3 - client/src/pages/index.jsx | 14 +- client/src/pages/onboarding.jsx | 5 - .../src/pages/photostory/[...photostory].jsx | 6 +- client/src/pages/portfolio/[...userId].jsx | 24 +- client/src/pages/search.jsx | 7 +- client/src/screens/404.js | 2 +- client/src/screens/Archive.jsx | 7 +- client/src/screens/Live.js | 15 +- client/src/screens/Onboarding.js | 180 +++++++---- client/src/screens/admin/Admin.js | 2 +- client/src/screens/admin/Browse.js | 16 +- client/src/store/actions/index.js | 5 - client/src/store/actions/types.js | 5 - client/src/store/reducers/index.js | 1 - client/src/utils/getAccess.js | 2 +- client/src/utils/getAdminRoutes.js | 2 +- client/yarn.lock | 5 - 65 files changed, 1382 insertions(+), 624 deletions(-) create mode 100644 client/src/components/widgets/UserAvatar.js delete mode 100644 client/src/config/ApolloClient.js delete mode 100644 client/src/config/Root.js create mode 100644 client/src/config/imagekit.js create mode 100644 client/src/context/ApolloContextProvider.js create mode 100644 client/src/context/AuthContextProvider.js rename client/src/context/{SidebarContext.jsx => SidebarContextProvider.jsx} (100%) delete mode 100644 client/src/context/auth/AuthContext.js delete mode 100644 client/src/context/auth/AuthState.js create mode 100644 client/src/graphql/mutations/user/addNITRMail.js create mode 100644 client/src/graphql/mutations/user/newsLetterSubscription.js create mode 100644 client/src/graphql/mutations/user/updateUserProfilePicture.js create mode 100644 client/src/graphql/mutations/user/updateUserTopics.js create mode 100644 client/src/graphql/queries/user/checkNITRMail.js create mode 100644 client/src/graphql/queries/user/getFirebaseUserByEmail.js create mode 100644 client/src/middleware.js delete mode 100644 client/src/store/actions/index.js delete mode 100644 client/src/store/actions/types.js delete mode 100644 client/src/store/reducers/index.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 9a3ac80d..86085257 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ }, { "name": "Next.js: debug client-side", - "type": "pwa-msedge", + "type": "msedge", "request": "launch", "url": "http://localhost:3000", "cwd": "${workspaceFolder}/client" @@ -21,7 +21,18 @@ "request": "launch", "command": "yarn start", "cwd": "${workspaceFolder}/client", - "console": "integratedTerminal", + "serverReadyAction": { + "pattern": "started server on .+, url: (https?://.+)", + "uriFormat": "%s", + "action": "debugWithEdge" + } + }, + { + "name": "Next.js: debug production stack", + "type": "node-terminal", + "request": "launch", + "command": "yarn start:prod", + "cwd": "${workspaceFolder}/client", "serverReadyAction": { "pattern": "started server on .+, url: (https?://.+)", "uriFormat": "%s", diff --git a/client/env/.env.sample b/client/env/.env.sample index bdad6248..b8f03963 100644 --- a/client/env/.env.sample +++ b/client/env/.env.sample @@ -5,6 +5,7 @@ PORT=3000 # ----- ----- APOLLO GRAPHQL ----- ----- NEXT_PUBLIC_SERVER_ADDRESS=some-server-url +SERVER_ACCESS_API_KEY=some-api-key # ----- ----- IMAGEKIT ----- ----- diff --git a/client/src/assets/placeholder/onboarding.js b/client/src/assets/placeholder/onboarding.js index 93a7f959..774bcc5f 100644 --- a/client/src/assets/placeholder/onboarding.js +++ b/client/src/assets/placeholder/onboarding.js @@ -18,8 +18,8 @@ export const ONBOARDING = Object.freeze({ EMAIL_PLACEHOLDER: 'instituteID@nitrkl.ac.in', BUTTON: { PRIMARY: 'Get Verification Link', - SECONDARY: 'Check again and continue', - MOBILE: 'Next', + SECONDARY: 'Open webmail', + FINALLY: 'Confirm Verification', }, NOTE: 'Note: This only works if you’re a current student/ employee of \n NIT Rourkela with a valid institute email id.', }, @@ -28,27 +28,26 @@ export const ONBOARDING = Object.freeze({ TITLE: 'Interested Topics', CONTENT: "Select the topics you're interested in, and get smarter article suggestions on the MM website!", + LOADING_CONTENT: + "Give us a moment while we save your choices. Wouldn't want to mix them up.", }, TOPICS: [ - 'Witsdom', - 'Campus Buzz', - 'Alumni Affairs', - 'Student Activities', - 'Halls', - 'Interviews', - 'SAC Speaks', - 'Dean Speaks', - 'Videos', - 'Guest Interviews', - "Director's Desk", - "Chief Warden's Column", - 'Comics', - 'Alumnus Speaks', - 'Editorial', - 'Placements', - 'Internships', - 'Higher Education', - 'Photostories', + ['Campus Buzz', 12], + ['Alumni Affairs', 52], + ['Student Activities', 14], + ['Halls', 15], + ['Interviews', 21], + ['SAC Speaks', 22], + ['Guest Interviews', 24], + ["Director's Desk", 31], + ["Chief Warden's Column", 32], + ['Alumnus Speaks', 51], + ['Editorial', 66], + ['Placements', 41], + ['Internships', 42], + ['Higher Education', 43], + ['Photostories', 62], + ['Witsdom', 61], ], }, NEWSLETTER: { diff --git a/client/src/components/admin/Sidebar/menulist/NavCollapse.jsx b/client/src/components/admin/Sidebar/menulist/NavCollapse.jsx index a8ed85cf..84bc4586 100644 --- a/client/src/components/admin/Sidebar/menulist/NavCollapse.jsx +++ b/client/src/components/admin/Sidebar/menulist/NavCollapse.jsx @@ -9,11 +9,9 @@ import { ListItemText, Typography, } from '@mui/material'; -import { - FiberManualRecord, - KeyboardArrowDown, - KeyboardArrowUp, -} from '@mui/icons-material'; +import FiberManualRecord from '@mui/icons-material/FiberManualRecord'; +import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUp from '@mui/icons-material/KeyboardArrowUp'; // components import NavItem from './NavItem'; diff --git a/client/src/components/admin/Sidebar/menulist/NavItem.jsx b/client/src/components/admin/Sidebar/menulist/NavItem.jsx index 13f26c4d..bfdadace 100644 --- a/client/src/components/admin/Sidebar/menulist/NavItem.jsx +++ b/client/src/components/admin/Sidebar/menulist/NavItem.jsx @@ -11,7 +11,7 @@ import { } from '@mui/material'; // context -import { SidebarContext } from '../../../../context/SidebarContext'; +import { SidebarContext } from '../../../../context/SidebarContextProvider'; const NavItem = ({ item, level }) => { const theme = useTheme(); diff --git a/client/src/components/homepage/SocialMedia.js b/client/src/components/homepage/SocialMedia.js index 0e3cebc6..261a113c 100644 --- a/client/src/components/homepage/SocialMedia.js +++ b/client/src/components/homepage/SocialMedia.js @@ -15,7 +15,8 @@ import insta4 from '../../assets/images/instagram/insta4.jpeg'; import insta5 from '../../assets/images/instagram/insta5.jpeg'; import insta6 from '../../assets/images/instagram/insta6.jpeg'; import Image from 'next/image'; -import { YouTube, Instagram } from '@mui/icons-material'; +import YouTube from '@mui/icons-material/YouTube'; +import Instagram from '@mui/icons-material/Instagram'; const INSTA_LINKS = [insta1, insta2, insta3, insta4, insta5, insta6]; diff --git a/client/src/components/marginals/DesktopNavbar.js b/client/src/components/marginals/DesktopNavbar.js index a49711f5..878c927b 100644 --- a/client/src/components/marginals/DesktopNavbar.js +++ b/client/src/components/marginals/DesktopNavbar.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useContext } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { useRouter } from 'next/router'; @@ -7,13 +7,20 @@ import { useRouter } from 'next/router'; import InputAdornment from '@mui/material/InputAdornment'; import SearchIcon from '@mui/icons-material/Search'; import makeStyles from '@mui/styles/makeStyles'; -import { Container, Typography, TextField, Fade } from '@mui/material'; +import { + Container, + Typography, + TextField, + ButtonBase, + Fade, +} from '@mui/material'; // import TrendingUpSharpIcon from '@mui/icons-material/TrendingUpSharp'; // Utils import ROUTES from '../../utils/getRoutes'; import NewTabLink from '../shared/links/NewTabLink'; import getArticleLink from '../../utils/getArticleLink'; +import { authContext } from '../../context/AuthContextProvider'; // Assets import logoFullBlack from '../../assets/images/logos/logo_full_black.png'; @@ -21,13 +28,16 @@ import logoFullBlack from '../../assets/images/logos/logo_full_black.png'; //hooks import useAutoComplete from '../../hooks/useAutoComplete'; +//components +import UserAvatar from '../widgets/UserAvatar'; + const DesktopNavbar = () => { const router = useRouter(); const [search, setSearch] = useState(''); const [isSearchActive, setIsSearchActive] = useState(false); const inputRef = useRef(null); const classes = useStyles({ isSearchActive }); - + const { user } = useContext(authContext); const autoCompleteData = useAutoComplete(search, 10); useEffect(() => { @@ -120,21 +130,38 @@ const DesktopNavbar = () => { objectFit='cover' /> - - - - - ), - }} - /> +
+ + + + ), + }} + /> +
+ {user ? ( +
+ +
+ ) : ( + + + Sign In + + + )} +
+
    @@ -166,16 +193,32 @@ const useStyles = makeStyles((theme) => ({ width: '100%', marginTop: '10px', }, - detailsContainer: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', - paddingTop: '10px', paddingBottom: '25px', borderBottom: `3px solid ${theme.palette.secondary.neutral50}`, }, + button: { + textAlign: 'center', + borderRadius: '4px', + border: 'solid black 0.5px', + margin: '8px 8px 0px 0px', + padding: '10px 20px', + }, + avatar: { + marginRight: '20px', + }, + label: { + fontFamily: 'Source Sans Pro', + fontSize: '20px', + fontWeight: '400', + lineHeight: '1.2rem', + textDecoration: 'none', + color: theme.palette.secondary.main, + }, imgContainer: { width: '33%', height: 'auto', @@ -188,6 +231,13 @@ const useStyles = makeStyles((theme) => ({ width: 'auto !important', height: 'auto !important', }, + searchAndSign: { + display: 'flex', + alignItems: 'center', + }, + signIn: { + paddingLeft: '30px', + }, menuContainer: { width: '100%', marginTop: '20px', diff --git a/client/src/components/marginals/Footer.js b/client/src/components/marginals/Footer.js index 690f1e8e..0cca6806 100644 --- a/client/src/components/marginals/Footer.js +++ b/client/src/components/marginals/Footer.js @@ -209,8 +209,9 @@ const Footer = () => { name='month' className={classes.archivesSelect} onChange={(e) => setMonth(e.target.value)} + defaultValue={''} > - {ARCHIVES.months.map((month, key) => ( @@ -223,8 +224,9 @@ const Footer = () => { name='year' className={classes.archivesSelect} onChange={(e) => setYear(e.target.value)} + defaultValue={''} > - {ARCHIVES.years.map((year, key) => ( diff --git a/client/src/components/marginals/TopBar.js b/client/src/components/marginals/TopBar.js index e17cf430..a2f6afce 100644 --- a/client/src/components/marginals/TopBar.js +++ b/client/src/components/marginals/TopBar.js @@ -1,31 +1,56 @@ -import React from 'react'; +import React, { useContext } from 'react'; import Link from 'next/link'; import makeStyles from '@mui/styles/makeStyles'; -import { Container } from '@mui/material'; +import { Container, useMediaQuery } from '@mui/material'; +import theme from '../../config/themes/light'; + +import { authContext } from '../../context/AuthContextProvider'; const TopBar = () => { const classes = useStyles(); + const isMobileOrTablet = useMediaQuery(theme.breakpoints.down('md')); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + const { user, logout } = useContext(authContext); + return (
      -
    • - - About - -
    • -
    • - - Guide - -
    • -
    • - - Contact Us - -
    • + {!isMobile && ( + <> +
    • + + About + +
    • +
    • + + Guide + +
    • +
    • + + Contact Us + +
    • + + )} + {isMobileOrTablet && ( +
    • + {user ? ( + + Sign Out + + ) : ( + + Sign In + + )} +
    • + )}
    @@ -35,6 +60,16 @@ const TopBar = () => { export default TopBar; const useStyles = makeStyles((theme) => ({ + signInButton: { + color: '#ffffff', + backgroundColor: '#00368D', + alignItems: 'center', + padding: '4px 6px', + borderRadius: '0.3em', + fontFamily: 'IBM Plex Sans', + fontSize: '14px', + fontWeight: '400', + }, topBar: { marginTop: '0', backgroundColor: theme.palette.primary.blue50, @@ -49,6 +84,9 @@ const useStyles = makeStyles((theme) => ({ paddingRight: '16px', marginLeft: 'auto', marginRight: 'auto', + [theme.breakpoints.down('sm')]: { + padding: '0px', + }, }, navList: { margin: '0', @@ -64,6 +102,7 @@ const useStyles = makeStyles((theme) => ({ marginLeft: '20px', fontSize: '1rem', }, + cursor: 'pointer', }, navLink: { fontFamily: 'IBM Plex Sans', diff --git a/client/src/components/onboarding/Pagination.js b/client/src/components/onboarding/Pagination.js index d3cdc611..831eb406 100644 --- a/client/src/components/onboarding/Pagination.js +++ b/client/src/components/onboarding/Pagination.js @@ -42,7 +42,6 @@ function Pagination({ stages, active }) { ); } - export default Pagination; const useStyles = makeStyles((theme) => ({ diff --git a/client/src/components/onboarding/stages/NewsletterSignup.js b/client/src/components/onboarding/stages/NewsletterSignup.js index a57bc69e..d91cf2aa 100644 --- a/client/src/components/onboarding/stages/NewsletterSignup.js +++ b/client/src/components/onboarding/stages/NewsletterSignup.js @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { useRouter } from 'next/router'; +import React, { useState, useContext } from 'react'; +// import { useRouter } from 'next/router'; import Image from 'next/image'; // Library @@ -11,25 +11,40 @@ import makeStyles from '@mui/styles/makeStyles'; // Components import Button from '../../shared/button/Regular'; -import Input from '../../shared/input/Regular'; // Assets import newsletter from '../../../assets/images/onboarding/newsletter.png'; import { ONBOARDING } from '../../../assets/placeholder/onboarding'; -function VerifyEmail(props) { +//graphql +import newsLetterSubscription from '../../../graphql/mutations/user/newsLetterSubscription'; + +//context +import { authContext } from '../../../context/AuthContextProvider'; +import { apolloContext } from '../../../context/ApolloContextProvider'; + +function NewsletterSignup({ onComplete, onSkip, tabletMatches }) { const classes = useStyles(); - const router = useRouter(); + // const router = useRouter(); + const graphClient = useContext(apolloContext); + // Local States const [isSigned, setIsSigned] = useState(false); - // Props - const { email, setEmail, signupNewsletter, onNext, onBack, tabletMatches } = - props; + const { + user: { mid }, + } = useContext(authContext); - const onSignup = () => { + const onSignup = async () => { setIsSigned(true); - signupNewsletter(); + + await graphClient.mutate({ + mutation: newsLetterSubscription, + variables: { + userId: mid, + flag: true, + }, + }); }; return ( @@ -66,15 +81,6 @@ function VerifyEmail(props) { /> )} - - {ONBOARDING.NEWSLETTER.SECONDARY.TITLE} - - )} @@ -95,14 +101,7 @@ function VerifyEmail(props) { - Back - - Skip @@ -116,7 +115,7 @@ function VerifyEmail(props) { ? ONBOARDING.NEWSLETTER.BUTTON.MOBILE : ONBOARDING.NEWSLETTER.BUTTON.PRIMARY } - onClick={isSigned ? onNext : onSignup} + onClick={isSigned ? onComplete : onSignup} containerStyles={classes.button} /> @@ -124,7 +123,7 @@ function VerifyEmail(props) { ); } -export default VerifyEmail; +export default NewsletterSignup; const useStyles = makeStyles((theme) => ({ container: { @@ -162,14 +161,6 @@ const useStyles = makeStyles((theme) => ({ fontSize: '14px', }, }, - emailInput: { - width: '85%', - borderRadius: '20px', - paddingLeft: '20px', - [theme.breakpoints.down('md')]: { - width: '100%', - }, - }, buttonContainer: { display: 'flex', alignItems: 'center', @@ -205,10 +196,6 @@ const useStyles = makeStyles((theme) => ({ icon: { color: theme.palette.accent.green, }, - skip: { - cursor: 'pointer', - color: theme.palette.secondary.neutral50, - }, back: { marginRight: 'auto', cursor: 'pointer', diff --git a/client/src/components/onboarding/stages/SelectTopics.js b/client/src/components/onboarding/stages/SelectTopics.js index 68100234..50b2fbe6 100644 --- a/client/src/components/onboarding/stages/SelectTopics.js +++ b/client/src/components/onboarding/stages/SelectTopics.js @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useContext, useState } from 'react'; +import { authContext } from '../../../context/AuthContextProvider'; // Hooks import useToggle from '../../../hooks/useToggle'; @@ -13,15 +14,27 @@ import Button from '../../shared/button/Regular'; // Constants import { ONBOARDING } from '../../../assets/placeholder/onboarding'; -const Topic = (props) => { +//graphql +import updateUserTopics from '../../../graphql/mutations/user/updateUserTopics'; +import { apolloContext } from '../../../context/ApolloContextProvider'; + +function Topic({ + topic, + addSelectedTopic, + removeSelectedTopic, + tabletMatches, + setSnackbarData, +}) { const [selected, toggleSelected] = useToggle(false); const classes = useStyles(selected); - // Props - const { topic, addSelectedTopic, removeSelectedTopic, tabletMatches } = props; const onClick = () => { - selected ? removeSelectedTopic(topic) : addSelectedTopic(topic); + selected ? removeSelectedTopic(topic[1]) : addSelectedTopic(topic[1]); toggleSelected(); + setSnackbarData({ + message: '', + severity: 'success', + }); }; return ( @@ -30,22 +43,60 @@ const Topic = (props) => { className={classes.topicName} variant={tabletMatches ? 'body2' : 'body1'} > - {topic} + {topic[0]} ); -}; +} -function SelectTopics(props) { +function SelectTopics({ onComplete, onSkip, tabletMatches, setSnackbarData }) { const classes = useStyles(); - // props - const { - selectedTopics, - addSelectedTopic, - removeSelectedTopic, - onNext, - tabletMatches, - } = props; + const [selectedTopics, setSelectedTopics] = useState([]); + + const addSelectedTopic = (newTopic) => + setSelectedTopics((current) => [...current, newTopic]); + + const removeSelectedTopic = (topic) => + setSelectedTopics((selected) => { + return selected.filter((selectedTopic) => { + if (selectedTopic !== topic) return selectedTopic; + }); + }); + + const { user } = useContext(authContext); + const graphClient = useContext(apolloContext); + + const updateInterestedTopics = async (topics) => { + try { + if (!user) { + setSnackbarData({ + message: 'Please sign up to continue.', + severity: 'warning', + }); + return; + } + if (topics.length === 0) { + onSkip(); + return; + } + console.log(user); + + await graphClient.mutate({ + mutation: updateUserTopics, + variables: { + id: user.mid, + interestedTopics: topics, + }, + }); + + onComplete(); + } catch (error) { + setSnackbarData({ + message: 'Something went wrong. Please try again.', + severity: 'error', + }); + } + }; return (
    @@ -66,6 +117,7 @@ function SelectTopics(props) { addSelectedTopic={addSelectedTopic} removeSelectedTopic={removeSelectedTopic} tabletMatches={tabletMatches} + setSnackbarData={setSnackbarData} /> ))}
    @@ -73,8 +125,7 @@ function SelectTopics(props) {
    ); } @@ -66,7 +115,8 @@ export default Welcome; const useStyles = makeStyles((theme) => ({ container: { width: '100%', - height: '100%', + minHeight: '60%', + maxHeight: '100%', padding: '32px 12px', display: 'flex', flexDirection: 'column', diff --git a/client/src/components/shared/button/ScrollToTopButton.js b/client/src/components/shared/button/ScrollToTopButton.js index 0971c26f..62e6f947 100644 --- a/client/src/components/shared/button/ScrollToTopButton.js +++ b/client/src/components/shared/button/ScrollToTopButton.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import makeStyles from '@mui/styles/makeStyles'; import { Fab } from '@mui/material'; -import { KeyboardArrowUp } from '@mui/icons-material'; +import KeyboardArrowUp from '@mui/icons-material/KeyboardArrowUp'; const ScrollToTopButton = () => { const classes = useStyles(); @@ -26,6 +26,7 @@ const ScrollToTopButton = () => { useEffect(() => { window.addEventListener('scroll', toggleVisible); + return () => window.removeEventListener('scroll', toggleVisible); }, []); return ( diff --git a/client/src/components/widgets/HomeCarousel.js b/client/src/components/widgets/HomeCarousel.js index 4d20bfe3..f8aad5b3 100644 --- a/client/src/components/widgets/HomeCarousel.js +++ b/client/src/components/widgets/HomeCarousel.js @@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react'; import makeStyles from '@mui/styles/makeStyles'; import { useSwipeable } from 'react-swipeable'; -import { ArrowBack, ArrowForward } from '@mui/icons-material'; +import ArrowBack from '@mui/icons-material/ArrowBack'; +import ArrowForward from '@mui/icons-material/ArrowForward'; const HomeCarousel = ({ links }) => { const classes = useStyles(); diff --git a/client/src/components/widgets/PostHolder.js b/client/src/components/widgets/PostHolder.js index ea6f4c60..fb6cbef2 100644 --- a/client/src/components/widgets/PostHolder.js +++ b/client/src/components/widgets/PostHolder.js @@ -1,6 +1,6 @@ import React from 'react'; -import { PhoneOutlined } from '@mui/icons-material'; +import PhoneOutlined from '@mui/icons-material/PhoneOutlined'; import { Grid, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; diff --git a/client/src/components/widgets/SocietyCards.js b/client/src/components/widgets/SocietyCards.js index 31ae15e5..9083b114 100644 --- a/client/src/components/widgets/SocietyCards.js +++ b/client/src/components/widgets/SocietyCards.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import clsx from 'clsx'; -import { ExpandMore } from '@mui/icons-material'; +import ExpandMore from '@mui/icons-material/ExpandMore'; import { Card, CardActions, diff --git a/client/src/components/widgets/Squiggles.js b/client/src/components/widgets/Squiggles.js index 019918df..eab4f7c6 100644 --- a/client/src/components/widgets/Squiggles.js +++ b/client/src/components/widgets/Squiggles.js @@ -22,11 +22,11 @@ function Squiggles({ data }) {
    -

    +

    {data.content} -

    +
    diff --git a/client/src/components/widgets/UserAvatar.js b/client/src/components/widgets/UserAvatar.js new file mode 100644 index 00000000..ef7e719a --- /dev/null +++ b/client/src/components/widgets/UserAvatar.js @@ -0,0 +1,68 @@ +import React, { useState, useContext } from 'react'; + +import Avatar from '@mui/material/Avatar'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import Logout from '@mui/icons-material/Logout'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import makeStyles from '@mui/styles/makeStyles'; + +import { authContext } from '../../context/AuthContextProvider'; + +const UserAvatar = ({ picture, name }) => { + const classes = useStyles(); + + const [anchorEl, setAnchorEl] = useState(null); + const { logout } = useContext(authContext); + + const handleMenuOpen = (event) => { + setAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handleLogOut = () => { + logout(); + handleMenuClose(); + }; + + return ( +
    + + + + + + + Logout + + +
    + ); +}; + +export default UserAvatar; + +const useStyles = makeStyles(() => ({ + avatarContainer: { + position: 'relative', + display: 'inline-block', + }, + avatar: { + cursor: 'pointer', + }, +})); diff --git a/client/src/config/ApolloClient.js b/client/src/config/ApolloClient.js deleted file mode 100644 index 9db0d17a..00000000 --- a/client/src/config/ApolloClient.js +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-disable no-console */ -import React from 'react'; -import { - ApolloClient, - InMemoryCache, - ApolloProvider, - HttpLink, - from, -} from '@apollo/client'; -import { setContext } from '@apollo/client/link/context'; -import { onError } from '@apollo/client/link/error'; -import { parseCookies } from 'nookies'; - -const cache = new InMemoryCache(); - -const errorLink = onError(({ graphQLErrors, networkError }) => { - if (graphQLErrors) { - graphQLErrors.map(({ message, location, path }) => - // console.log(new Error({ message, location, path })), - console.error({ message, location, path }), - ); - } else if (networkError) { - const { message, name, respose, result, bodyText, stack, statusCode } = - networkError; - console.log( - new Error({ - message, - name, - respose, - result, - bodyText, - stack, - statusCode, - }), - ); - } -}); - -const link = from([ - errorLink, - new HttpLink({ - uri: `${process.env.NEXT_PUBLIC_SERVER_ADDRESS}/v1/graph`, - }), -]); - -const getApolloLink = (token) => { - const cookies = parseCookies(); - const authLink = setContext((_, { headers }) => ({ - headers: { - ...headers, - Authorization: token || cookies.firebaseToken || '', - }, - })); - return authLink.concat(link); -}; - -const client = new ApolloClient({ - cache, - link: getApolloLink(), - name: 'monday-morning-client', - version: '1.3', - queryDeduplication: false, - defaultOptions: { - watchQuery: { - fetchPolicy: 'cache-and-network', - errorPolicy: 'ignore', - }, - query: { - fetchPolicy: 'network-only', - errorPolicy: 'all', - }, - mutate: { - errorPolicy: 'all', - }, - }, -}); - -const ProviderWrapper = ({ children }) => { - return {children}; -}; - -export { client as GraphClient, getApolloLink }; - -export default ProviderWrapper; diff --git a/client/src/config/Root.js b/client/src/config/Root.js deleted file mode 100644 index 609be105..00000000 --- a/client/src/config/Root.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { createStore, applyMiddleware, compose } from 'redux'; -import { Provider } from 'react-redux'; -import thunk from 'redux-thunk'; - -import reducers from '../store/reducers'; - -export default ({ initialState = {}, children }) => { - const composeEnhancers = - window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - - const store = createStore( - reducers, - initialState, - composeEnhancers(applyMiddleware(thunk)), - ); - - return {children}; -}; diff --git a/client/src/config/imagekit.js b/client/src/config/imagekit.js new file mode 100644 index 00000000..7d21050b --- /dev/null +++ b/client/src/config/imagekit.js @@ -0,0 +1,9 @@ +import ImageKit from 'imagekit-javascript'; + +const imagekit = new ImageKit({ + publicKey: process.env.NEXT_PUBLIC_IMAGEKIT_PUBLIC_KEY, + urlEndpoint: process.env.NEXT_PUBLIC_IMAGEKIT_URLENDPOINT, + authenticationEndpoint: `${process.env.NEXT_PUBLIC_SERVER_ADDRESS}${process.env.NEXT_PUBLIC_IMAGEKIT_AUTHENTICATION_ENDPOINT}`, +}); + +export default imagekit; diff --git a/client/src/context/ApolloContextProvider.js b/client/src/context/ApolloContextProvider.js new file mode 100644 index 00000000..55c6187d --- /dev/null +++ b/client/src/context/ApolloContextProvider.js @@ -0,0 +1,130 @@ +import { + ApolloClient, + ApolloProvider, + HttpLink, + InMemoryCache, + from, + getApolloContext, +} from '@apollo/client'; +import { setContext } from '@apollo/client/link/context'; +import { onError } from '@apollo/client/link/error'; +import { parseCookies, setCookie, destroyCookie } from 'nookies'; +import React, { useContext, useEffect } from 'react'; +import { authContext } from './AuthContextProvider'; + +export const apolloContext = getApolloContext(); + +const cache = new InMemoryCache(); + +const errorLink = onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) { + graphQLErrors.map(({ message, location, path }) => + console.error({ message, location, path }), + ); + } else if (networkError) { + const { message, name, respose, result, bodyText, stack, statusCode } = + networkError; + console.log( + new Error({ + message, + name, + respose, + result, + bodyText, + stack, + statusCode, + }), + ); + } +}); + +const link = from([ + errorLink, + new HttpLink({ + uri: `${process.env.NEXT_PUBLIC_SERVER_ADDRESS}/v1/graph`, + credentials: 'include', + }), +]); + +const getApolloLink = (useApiKey = false, token) => { + const cookies = parseCookies(); + + const authLink = setContext((_, { headers }) => + useApiKey + ? { + headers: { + ...headers, + 'x-api-key': process.env.SERVER_ACCESS_API_KEY, + }, + } + : { + headers: { + ...headers, + Authorization: token || cookies?.firebaseToken || '', + }, + }, + ); + return authLink.concat(link); +}; + +const getGraphClient = (useApiKey = false, token) => { + return new ApolloClient({ + cache, + link: getApolloLink(useApiKey, token), + name: 'monday-morning-client', + version: '1.3', + queryDeduplication: false, + defaultOptions: { + watchQuery: { + fetchPolicy: 'cache-and-network', + errorPolicy: 'ignore', + }, + query: { + fetchPolicy: 'network-only', + errorPolicy: 'all', + }, + mutate: { + errorPolicy: 'all', + }, + }, + }); +}; + +export { getApolloLink, getGraphClient }; + +const ApolloContextProvider = ({ children }) => { + const { user } = useContext(authContext); + + const client = getGraphClient(false, user?.firebaseToken); + + useEffect(() => { + console.log('apolloClient', user?.firebaseToken); + + client.setLink( + getApolloLink(false, user?.firebaseToken ?? user?.accessToken), + ); + + if (user?.firebaseToken || user?.accessToken) { + setCookie( + null, + 'firebaseToken', + user?.firebaseToken ?? user?.accessToken, + { + secure: true, + sameSite: true, + maxAge: 3600, + }, + ); + } else { + destroyCookie(null, 'firebaseToken'); + } + }, [user]); + + return ( + + {children} + + ); +}; + +export default ApolloContextProvider; diff --git a/client/src/context/AuthContextProvider.js b/client/src/context/AuthContextProvider.js new file mode 100644 index 00000000..ae2719b1 --- /dev/null +++ b/client/src/context/AuthContextProvider.js @@ -0,0 +1,290 @@ +import React, { createContext, useEffect, useState } from 'react'; + +import { getAnalytics, isSupported } from 'firebase/analytics'; +import { + EmailAuthProvider, + GoogleAuthProvider, + getAdditionalUserInfo, + getIdToken, + isSignInWithEmailLink, + linkWithCredential, + onIdTokenChanged, + sendSignInLinkToEmail, + signInWithCredential, +} from 'firebase/auth'; +import { destroyCookie, setCookie } from 'nookies'; +import checkNITRMail from '../graphql/queries/user/checkNITRMail'; +import { auth, firebaseApp } from '../config/firebase'; +import imagekit from '../config/imagekit'; +import addNITRMail from '../graphql/mutations/user/addNITRMail'; +import registerUser from '../graphql/mutations/user/registerUser'; +import updateUserProfilePicture from '../graphql/mutations/user/updateUserProfilePicture'; +import getFirebaseUserByEmail from '../graphql/queries/user/getFirebaseUserByEmail'; +import { getApolloLink, getGraphClient } from './ApolloContextProvider'; + +export const authContext = createContext(); + +const AuthContextProvider = ({ children }) => { + const [user, setUser] = useState(null); + + const graphClient = getGraphClient(false, user?.firebaseToken); + + useEffect(() => { + if (isSupported() && process.env.NODE_ENV === 'production') { + getAnalytics(firebaseApp); + } + if (auth) { + onIdTokenChanged(auth, async (_user) => { + console.log('onIdTokenChanged', _user); + + if (!_user) { + setUser(null); + return; + } + + const firebaseToken = await getIdToken(_user, false); + + setUser((_u) => ({ + ...(user ?? {}), + ...(_u ?? {}), + ..._user, + firebaseToken, + })); + }); + } + }, []); + + useEffect(() => { + console.log('user', user); + + graphClient.setLink( + getApolloLink(false, user?.firebaseToken ?? user?.accessToken), + ); + + if (user?.firebaseToken || user?.accessToken) { + setCookie( + null, + 'firebaseToken', + user?.firebaseToken ?? user?.accessToken, + { + secure: true, + sameSite: true, + maxAge: 3600, + }, + ); + } else { + destroyCookie(null, 'firebaseToken'); + } + }, [user]); + + const loginWithToken = async (_token) => { + try { + const _credential = GoogleAuthProvider.credential(_token); + + const res = await signInWithCredential(auth, _credential); + + const { user: _user } = res; + + const _firebaseToken = await getIdToken(_user, false); + graphClient.setLink(getApolloLink(false, _firebaseToken)); + + setUser((_u) => ({ + ..._user, + firebaseToken: _firebaseToken, + refreshToken: _user.refreshToken, + })); + + if (!getAdditionalUserInfo(res).isNewUser) { + const { + data: { getFirebaseUserByEmail: _fbUser }, + } = await graphClient.query({ + query: getFirebaseUserByEmail, + variables: { + email: _user.email, + }, + }); + + setUser((_u) => ({ + ..._user, + firebaseToken: _firebaseToken, + refreshToken: _user.refreshToken, + mid: _fbUser?.customClaims?.mid, + roles: _fbUser?.customClaims?.roles, + })); + + return { + isNewUser: false, + user: { + ..._user, + firebaseToken: _firebaseToken, + refreshToken: _user.refreshToken, + mid: _fbUser?.customClaims?.mid, + roles: _fbUser?.customClaims?.roles, + }, + }; + } + + const _newAccount = await graphClient.mutate({ + mutation: registerUser, + variables: { + fullName: _user.displayName, + email: _user.email, + }, + }); + + if (!_newAccount?.data?.registerUser) { + throw new Error('Error Registering User'); + } + + const _refreshedFirebaseToken = await getIdToken(_user, true); + graphClient.setLink(getApolloLink(false, _refreshedFirebaseToken)); + + setUser((_u) => ({ + ..._user, + ...(_u ?? {}), + firebaseToken: _firebaseToken, + refreshToken: _user.refreshToken, + mid: _newAccount.data.registerUser.id, + })); + + if (_newAccount.data.registerUser.accountType === 2) { + return { + isNewUser: true, + isMigratedUser: true, + user: user, + newAccount: { ..._newAccount }, + }; + } + + let _imageUpload = { + filePath: '/user/default.png', + }; + + try { + const _userPicture = await (await fetch(_user.photoURL)).blob(); + + if (!['image/png', 'image/jpeg'].includes(_userPicture.type)) { + throw new Error('Invalid Image Type'); + } + + _imageUpload = await imagekit + .upload({ + file: _user.photoURL, + useUniqueFileName: false, + folder: '/user', + fileName: `${_newAccount.data.registerUser.id}.${ + _userPicture.type.toString().split('/')[1] + }`, + tags: [_user.uid, 'user', 'profilePicture'], + }) + .then((result) => { + return result; + }); + } catch (error) { + console.log(error); + } + + const _imageUpdateResponse = await graphClient.mutate({ + mutation: updateUserProfilePicture, + variables: { + id: _newAccount.data.registerUser.id, + storePath: _imageUpload.filePath, + }, + }); + + return { + isNewUser: true, + user: user, + newAccount: { ..._newAccount, ..._imageUpdateResponse }, + }; + } catch (error) { + return { + isNewUser: user?.isNewUser ?? false, + error, + }; + } + }; + + const sendEmailLink = async (email) => { + try { + const _actionCodeSettings = { + url: `${ + process.env.NODE_ENV === 'production' + ? 'https://mondaymorning.nitrkl.ac.in' + : window.location.origin + }/onboarding?stage=VERIFY_EMAIL&email=${email}&isEmailLink=true`, + handleCodeInApp: true, + }; + await sendSignInLinkToEmail(auth, email, _actionCodeSettings); + return true; + } catch (error) { + console.error(error); + return { error }; + } + }; + + const _isSignInWithEmailLink = (href) => isSignInWithEmailLink(auth, href); + + const attachNITREmail = async (nitrMail, href) => { + try { + const _credential = EmailAuthProvider.credentialWithLink(nitrMail, href); + const _userCredential = await linkWithCredential( + auth.currentUser, + _credential, + ); + + const _user = await graphClient.mutate({ + mutation: addNITRMail, + variables: { + email: user.email, + nitrMail, + }, + }); + + // await getIdToken(auth.currentUser, true); + + return { userCredential: _userCredential, user: _user }; + } catch (error) { + console.error(error); + return { error }; + } + }; + + const checkNITREmail = async (email) => { + try { + const { data } = await graphClient.query({ + query: checkNITRMail, + variables: { + nitrMail: email, + }, + }); + + return data; + } catch (error) { + console.error(error); + return { error }; + } + }; + + const logout = () => { + auth.signOut(); + }; + + return ( + + {children} + + ); +}; + +export default AuthContextProvider; diff --git a/client/src/context/SidebarContext.jsx b/client/src/context/SidebarContextProvider.jsx similarity index 100% rename from client/src/context/SidebarContext.jsx rename to client/src/context/SidebarContextProvider.jsx diff --git a/client/src/context/auth/AuthContext.js b/client/src/context/auth/AuthContext.js deleted file mode 100644 index 2a48c242..00000000 --- a/client/src/context/auth/AuthContext.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -const authContext = createContext(); - -export default authContext; diff --git a/client/src/context/auth/AuthState.js b/client/src/context/auth/AuthState.js deleted file mode 100644 index 2beaacbe..00000000 --- a/client/src/context/auth/AuthState.js +++ /dev/null @@ -1,116 +0,0 @@ -import React, { useState, useEffect } from 'react'; - -//context -import authContext from './AuthContext'; - -//firebase -import { - GoogleAuthProvider, - signInWithCredential, - getIdToken, - onAuthStateChanged, - getAdditionalUserInfo, -} from 'firebase/auth'; -import { getAnalytics, isSupported } from 'firebase/analytics'; -import { auth, firebaseApp } from '../../config/firebase'; - -//graphql -import { getApolloLink, GraphClient } from '../../config/ApolloClient'; -import registerUser from '../../graphql/mutations/user/registerUser'; - -import ImageKit from 'imagekit-javascript'; -import { setCookie } from 'nookies'; - -const AuthState = ({ children }) => { - const [refreshToken, setRefreshToken] = useState(''); - const [user, setUser] = useState(null); - const [firebaseToken, setFirebaseToken] = useState(''); - - useEffect(() => { - if (isSupported() && process.env.NODE_ENV === 'production') { - getAnalytics(firebaseApp); - } - if (auth) { - onAuthStateChanged(auth, async (_user) => { - setUser(_user); - if (_user) setFirebaseToken(await getIdToken(_user, false)); - }); - } - }, []); - - useEffect(() => { - GraphClient.setLink(getApolloLink(firebaseToken)); - setCookie(null, 'firebaseToken', firebaseToken, { - secure: true, - sameSite: true, - maxAge: 3600, - }); - }, [user, firebaseToken, refreshToken]); - - const loginWithToken = async (_token) => { - const credential = GoogleAuthProvider.credential(_token); - - const res = await signInWithCredential(auth, credential); - - const { user, _tokenResponse } = res; - - GraphClient.setLink(getApolloLink(user.accessToken)); - setFirebaseToken(user.accessToken); - setRefreshToken(user.refreshToken); - setUser(user); - - if (getAdditionalUserInfo(res).isNewUser) { - try { - const imagekit = new ImageKit({ - publicKey: process.env.NEXT_PUBLIC_IMAGEKIT_PUBLIC_KEY, - urlEndpoint: process.env.NEXT_PUBLIC_IMAGEKIT_URLENDPOINT, - authenticationEndpoint: `${process.env.NEXT_PUBLIC_SERVER_ADDRESS}${process.env.NEXT_PUBLIC_IMAGEKIT_AUTHENTICATION_ENDPOINT}`, - }); - - const newAccount = await GraphClient.mutate({ - mutation: registerUser, - variables: { - fullName: user.displayName, - email: user.email, - }, - }); - console.log('Account Created ', newAccount); - - const userPicture = await (await fetch(user.photoURL)).blob(); - - if (!['image/png', 'image/jpeg'].includes(userPicture.type)) { - // throw error - } - - const imageUpload = await imagekit - .upload({ - file: user.photoURL, - folder: '/user', - fileName: `${newAccount.data.registerUser.id}.${ - userPicture.type.toString().split('/')[1] - }`, - tags: [user.uid, 'user', 'profilePicture'], - }) - .then((result) => { - console.log('Upload Success', result); - }); - } catch (err) { - console.log(err); - } - } - }; - - const logout = () => { - auth.signOut(); - }; - - return ( - - {children} - - ); -}; - -export default AuthState; diff --git a/client/src/graphql/mutations/user/addNITRMail.js b/client/src/graphql/mutations/user/addNITRMail.js new file mode 100644 index 00000000..30b90424 --- /dev/null +++ b/client/src/graphql/mutations/user/addNITRMail.js @@ -0,0 +1,15 @@ +import { gql } from '@apollo/client'; + +const addNITRMail = gql` + mutation addNITREmail($email: String!, $nitrMail: String!) { + addNITRMail(email: $email, nitrMail: $nitrMail) { + id + fullName + accountType + email + nitrMail + } + } +`; + +export default addNITRMail; diff --git a/client/src/graphql/mutations/user/newsLetterSubscription.js b/client/src/graphql/mutations/user/newsLetterSubscription.js new file mode 100644 index 00000000..2a815b23 --- /dev/null +++ b/client/src/graphql/mutations/user/newsLetterSubscription.js @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +const newsLetterSubscription = gql` + mutation ($userId: ID!, $flag: Boolean!) { + newsletterSubscription(id: $userId, flag: $flag) { + id + isNewsletterSubscribed + } + } +`; + +export default newsLetterSubscription; diff --git a/client/src/graphql/mutations/user/registerUser.js b/client/src/graphql/mutations/user/registerUser.js index 45548a97..1ad0d79c 100644 --- a/client/src/graphql/mutations/user/registerUser.js +++ b/client/src/graphql/mutations/user/registerUser.js @@ -6,6 +6,7 @@ export default gql` id fullName email + accountType } } `; diff --git a/client/src/graphql/mutations/user/updateUserProfilePicture.js b/client/src/graphql/mutations/user/updateUserProfilePicture.js new file mode 100644 index 00000000..8e71250f --- /dev/null +++ b/client/src/graphql/mutations/user/updateUserProfilePicture.js @@ -0,0 +1,26 @@ +import { gql } from '@apollo/client'; + +const updateUserProfilePicture = gql` + mutation UpdateUserProfilePicture( + $id: ID! + $store: Int + $storePath: String! + $blurhash: String + ) { + updateUserProfilePicture( + id: $id + store: $store + storePath: $storePath + blurhash: $blurhash + ) { + id + picture { + store + storePath + blurhash + } + } + } +`; + +export default updateUserProfilePicture; diff --git a/client/src/graphql/mutations/user/updateUserTopics.js b/client/src/graphql/mutations/user/updateUserTopics.js new file mode 100644 index 00000000..f7fc2819 --- /dev/null +++ b/client/src/graphql/mutations/user/updateUserTopics.js @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +const updateUserTopics = gql` + mutation UpdateUserTopics($id: ID!, $interestedTopics: [Int]!) { + updateUserTopics(id: $id, interestedTopics: $interestedTopics) { + id + interestedTopics + } + } +`; + +export default updateUserTopics; diff --git a/client/src/graphql/queries/user/checkNITRMail.js b/client/src/graphql/queries/user/checkNITRMail.js new file mode 100644 index 00000000..6b066e3b --- /dev/null +++ b/client/src/graphql/queries/user/checkNITRMail.js @@ -0,0 +1,10 @@ +import { gql } from '@apollo/client'; + +const checkNITRMail = gql` + query CheckNITRMail($nitrMail: String!) { + checkNITRMail(nitrMail: $nitrMail) { + nitrMail + } + } +`; +export default checkNITRMail; diff --git a/client/src/graphql/queries/user/getFirebaseUserByEmail.js b/client/src/graphql/queries/user/getFirebaseUserByEmail.js new file mode 100644 index 00000000..d3b8354a --- /dev/null +++ b/client/src/graphql/queries/user/getFirebaseUserByEmail.js @@ -0,0 +1,15 @@ +import { gql } from '@apollo/client'; + +const getFirebaseUserByEmail = gql` + query GetFirebaseUserByEmail($email: String!) { + getFirebaseUserByEmail(email: $email) { + email + customClaims { + mid + roles + } + } + } +`; + +export default getFirebaseUserByEmail; diff --git a/client/src/hooks/useAutoComplete.js b/client/src/hooks/useAutoComplete.js index 8b3598af..eb902285 100644 --- a/client/src/hooks/useAutoComplete.js +++ b/client/src/hooks/useAutoComplete.js @@ -1,10 +1,11 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useContext } from 'react'; //gql -import { GraphClient } from '../config/ApolloClient'; import getAutoComplete from '../graphql/queries/article/getAutoComplete'; +import { apolloContext } from '../context/ApolloContextProvider'; const useAutoComplete = (searchText, limit) => { + const graphClient = useContext(apolloContext); const [searchKeyword, setSearchKeyword] = useState(searchText); const [searchResult, setSearchResult] = useState([]); @@ -27,7 +28,7 @@ const useAutoComplete = (searchText, limit) => { (async () => { const { data: { getAutoComplete: autoCompleteResult }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getAutoComplete, variables: { keywords: searchKeyword, limit: limit }, }); diff --git a/client/src/middleware.js b/client/src/middleware.js new file mode 100644 index 00000000..e143ec12 --- /dev/null +++ b/client/src/middleware.js @@ -0,0 +1,55 @@ +import { NextResponse } from 'next/server'; + +export async function middleware(request) { + try { + const [, , articleId] = request.nextUrl.pathname.split('/'); + + if (!articleId) { + const url = request.nextUrl.clone(); + url.pathname = '/500'; + return NextResponse.rewrite(url); + } + + const cookies = { + firebaseToken: request.cookies?.get('firebaseToken'), + }; + + const queryString = `query getArticleByID { getArticleByID(id: "${articleId}") { id articleType title createdAt updatedAt }}`; + + const response = await fetch( + `${process.env.NEXT_PUBLIC_SERVER_ADDRESS}/v1/graph?query=${queryString}`, + { + headers: cookies.firebaseToken + ? { + Authorization: cookies.firebaseToken, + 'Content-Type': 'application/json', + } + : { + 'Content-Type': 'application/json', + }, + }, + ).then((res) => { + return res.json(); + }); + + if ( + response?.errors?.some((error) => + ['FORBIDDEN', 'NOT_FOUND'].includes(error?.message), + ) + ) { + const url = request.nextUrl.clone(); + url.pathname = '/404'; + return NextResponse.rewrite(url); + } + + return NextResponse.next(); + } catch (error) { + const url = request.nextUrl.clone(); + url.pathname = '/500'; + return NextResponse.rewrite(url); + } +} + +export const config = { + matcher: '/article/:articleId/:articleSlug?', +}; diff --git a/client/src/pages/404.jsx b/client/src/pages/404.jsx index 67b95471..7cb5e634 100644 --- a/client/src/pages/404.jsx +++ b/client/src/pages/404.jsx @@ -5,7 +5,7 @@ import Image from 'next/image'; import { Grid, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { ArrowForward } from '@mui/icons-material'; +import ArrowForward from '@mui/icons-material/ArrowForward'; import logo from '../assets/images/logo_mm.png'; import LINKS from '../utils/getLinks'; diff --git a/client/src/pages/500.jsx b/client/src/pages/500.jsx index b7794ca9..94a0496c 100644 --- a/client/src/pages/500.jsx +++ b/client/src/pages/500.jsx @@ -5,7 +5,7 @@ import Image from 'next/image'; import { Grid, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { ArrowForward } from '@mui/icons-material'; +import ArrowForward from '@mui/icons-material/ArrowForward'; import logo from '../assets/images/logo_mm.png'; import LINKS from '../utils/getLinks'; diff --git a/client/src/pages/[category]/[...subCategory].jsx b/client/src/pages/[category]/[...subCategory].jsx index 2f788465..bb4959c5 100644 --- a/client/src/pages/[category]/[...subCategory].jsx +++ b/client/src/pages/[category]/[...subCategory].jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; // Components import ActivityIndicator from '../../components/shared/ActivityIndicator'; @@ -209,6 +209,8 @@ export async function getStaticProps({ preview, }) { try { + const graphClient = getGraphClient(true); + const department = subCategory.length === 3 ? subCategory[1] : false; const pageNumber = subCategory.length === 3 ? subCategory[2] : subCategory[1]; @@ -238,11 +240,11 @@ export async function getStaticProps({ const { data: { getArticlesByCategories: articleList }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: [ - departmentDetails?.idNumber || subCategoryDetails?.idNumber, + departmentDetails?.idNumber || subCategoryDetails.idNumber, ], limit: 7, offset: 7 * (parseInt(pageNumber) - 1), @@ -251,11 +253,11 @@ export async function getStaticProps({ const { data: { countOfArticlesBySubCategory: countOfArticles }, - } = await GraphClient.query({ + } = await graphClient.query({ query: countOfArticlesBySubCategory, variables: { categoryNumber: - departmentDetails?.idNumber || subCategoryDetails?.idNumber, + departmentDetails?.idNumber || subCategoryDetails.idNumber, }, }); @@ -290,8 +292,8 @@ export async function getStaticPaths() { params: { category: path?.split('/')[1], subCategory: path?.split('/')[3] - ? [(path?.split('/')[2], path?.split('/')[3], '1')] - : [(path?.split('/')[2], '1')], + ? [path?.split('/')[2], path?.split('/')[3], '1'] + : [path?.split('/')[2], '1'], }, })); return { paths, fallback: true }; diff --git a/client/src/pages/[category]/index.jsx b/client/src/pages/[category]/index.jsx index f279bd9a..201e2d2b 100644 --- a/client/src/pages/[category]/index.jsx +++ b/client/src/pages/[category]/index.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; // Components import ActivityIndicator from '../../components/shared/ActivityIndicator'; @@ -175,13 +175,15 @@ export async function getStaticProps({ preview, }) { try { + const graphClient = getGraphClient(true); + const category = ROUTES.CATEGORIES.filter( ({ asyncRoutePath }) => asyncRoutePath === './Category', ).filter(({ shortName }) => shortName === categoryShortName)[0]; const { data: { getArticlesByCategories: articleList }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: [category?.idNumber, ...category?.subCategoryIds], diff --git a/client/src/pages/_app.jsx b/client/src/pages/_app.jsx index 2d9119b0..b0934ed7 100644 --- a/client/src/pages/_app.jsx +++ b/client/src/pages/_app.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import Head from 'next/head'; -import Router from 'next/router'; +// import Router from 'next/router'; // Styles import '../../public/index.css'; @@ -10,19 +10,17 @@ import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'; import { CssBaseline } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -// Providers -import ApolloClient from '../config/ApolloClient'; -import AuthState from '../context/auth/AuthState'; - // Components // import ScrollToTopOnMount from '../components/shared/ScrollToTopOnMount'; import ScrollToTopButton from '../components/shared/button/ScrollToTopButton'; import ActivityIndicator from '../components/shared/ActivityIndicator'; -import SidebarContextProvider from '../context/SidebarContext'; +import SidebarContextProvider from '../context/SidebarContextProvider'; // Theme import lightTheme from '../config/themes/light'; import { useRouter } from 'next/router'; -import { BrowserRouter } from 'react-router-dom'; +// import { BrowserRouter } from 'react-router-dom'; +import ApolloContextProvider from '../context/ApolloContextProvider'; +import AuthContextProvider from '../context/AuthContextProvider'; function TahitiApp({ Component, pageProps }) { const classes = useStyles(); @@ -48,8 +46,8 @@ function TahitiApp({ Component, pageProps }) { }, []); return ( - - + + {/* */} @@ -76,8 +74,8 @@ function TahitiApp({ Component, pageProps }) { - - + + ); } diff --git a/client/src/pages/archive/[...yearAndMonth].jsx b/client/src/pages/archive/[...yearAndMonth].jsx index 99a5998d..65b80304 100644 --- a/client/src/pages/archive/[...yearAndMonth].jsx +++ b/client/src/pages/archive/[...yearAndMonth].jsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import Head from 'next/head'; //liberaries -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; //components import Archive from '../../screens/Archive'; @@ -171,9 +171,11 @@ export async function getStaticProps({ }, }) { try { + const graphClient = getGraphClient(true); + const { data: { listArticlesByYearAndMonth: archiveArticles }, - } = await GraphClient.query({ + } = await graphClient.query({ query: listArticlesByYearAndMonth, variables: { onlyPublished: true, diff --git a/client/src/pages/article/[...article].jsx b/client/src/pages/article/[...article].jsx index e5a4b1d1..0bac74ab 100644 --- a/client/src/pages/article/[...article].jsx +++ b/client/src/pages/article/[...article].jsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import Head from 'next/head'; // Libraries -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; import STORES from '../../utils/getStores'; // Components @@ -199,10 +199,12 @@ export async function getStaticProps({ preview, }) { try { + const graphClient = getGraphClient(true); + if (oldArticleLink) { const { data: { getArticleByOldID: article }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticleByOldID, variables: { id: parseInt(oldArticleLink.split('-')[0]) }, }); @@ -223,7 +225,7 @@ export async function getStaticProps({ const { data: { getArticleByID: article }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticleByID, variables: { id: articleId }, }); diff --git a/client/src/pages/comingSoon.jsx b/client/src/pages/comingSoon.jsx index 809e87fb..b5f9a2d3 100644 --- a/client/src/pages/comingSoon.jsx +++ b/client/src/pages/comingSoon.jsx @@ -5,7 +5,7 @@ import Image from 'next/image'; import { Grid, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { ArrowForward } from '@mui/icons-material'; +import ArrowForward from '@mui/icons-material/ArrowForward'; import logo from '../assets/images/logo_mm.png'; import LINKS from '../utils/getLinks'; diff --git a/client/src/pages/expressions/[...subCategory].jsx b/client/src/pages/expressions/[...subCategory].jsx index 53f64519..86978284 100644 --- a/client/src/pages/expressions/[...subCategory].jsx +++ b/client/src/pages/expressions/[...subCategory].jsx @@ -7,7 +7,7 @@ import ActivityIndicator from '../../components/shared/ActivityIndicator'; import SubCategory from '../../screens/SubCategory'; import Marginals from '../../components/marginals/Marginals'; -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; // Utils import ROUTES from '../../utils/getRoutes'; @@ -208,13 +208,15 @@ export async function getStaticProps({ preview, }) { try { + const graphClient = getGraphClient(true); + let subCategoryDetails = ROUTES.SUB_CATEGORIES.OBJECT.EXPRESSIONS.filter( ({ shortName }) => shortName === subCategory, )[0]; const { data: { getArticlesByCategories: articleList }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: [subCategoryDetails?.idNumber], @@ -225,7 +227,7 @@ export async function getStaticProps({ const { data: { countOfArticlesBySubCategory: countOfArticles }, - } = await GraphClient.query({ + } = await graphClient.query({ query: countOfArticlesBySubCategory, variables: { categoryNumber: subCategoryDetails?.idNumber, diff --git a/client/src/pages/expressions/index.jsx b/client/src/pages/expressions/index.jsx index 9a9490b3..9c34ac03 100644 --- a/client/src/pages/expressions/index.jsx +++ b/client/src/pages/expressions/index.jsx @@ -4,7 +4,7 @@ import Head from 'next/head'; // Components import Marginals from '../../components/marginals/Marginals'; import Expressions from '../../screens/Expressions'; -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; import getArticlesByCategories from '../../graphql/queries/category/getArticlesByCategories'; import Custom500 from '../500'; @@ -170,16 +170,18 @@ const ExpressionsPage = ({ export async function getStaticProps() { try { + const graphClient = getGraphClient(true); + const { data: { getArticlesByCategories: witsdom }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: 61, limit: 3 }, }); const { data: { getArticlesByCategories: photostory }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: 62, limit: 5 }, }); @@ -188,7 +190,7 @@ export async function getStaticProps() { data: { getArticlesByCategories: [editorial], }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: 66, limit: 3 }, }); @@ -197,7 +199,7 @@ export async function getStaticProps() { data: { getArticlesByCategories: [miscellaneous], }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: 67, limit: 3 }, }); diff --git a/client/src/pages/guide.jsx b/client/src/pages/guide.jsx index b5c91ecc..385dfd6a 100644 --- a/client/src/pages/guide.jsx +++ b/client/src/pages/guide.jsx @@ -1,9 +1,6 @@ import React from 'react'; import Head from 'next/head'; -// // libraries -// import { GraphClient } from '../config/ApolloClient'; - // // Queries // import getLatestIssues from '../graphql/queries/homepage/getLatestIssues'; // import getLatestSquiggle from '../graphql/queries/homepage/getLatestSquiggle'; diff --git a/client/src/pages/index.jsx b/client/src/pages/index.jsx index 90e39325..7df7c1b9 100644 --- a/client/src/pages/index.jsx +++ b/client/src/pages/index.jsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { useRouter } from 'next/router'; import Head from 'next/head'; // libraries -import { GraphClient } from '../config/ApolloClient'; +import { getGraphClient } from '../context/ApolloContextProvider'; // Components import Marginals from '../components/marginals/Marginals'; @@ -180,9 +180,11 @@ function HomePage({ export async function getStaticProps({ preview }) { try { + const graphClient = getGraphClient(true); + const { data: { getLatestIssues: issues }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getLatestIssues, variables: { limit: 4 }, }); @@ -198,7 +200,7 @@ export async function getStaticProps({ preview }) { const { data: { getLatestSquiggle: squiggles }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getLatestSquiggle, }); @@ -206,7 +208,7 @@ export async function getStaticProps({ preview }) { data: { getArticlesByCategories: [witsdom], }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: 61, limit: 1 }, }); @@ -215,7 +217,7 @@ export async function getStaticProps({ preview }) { data: { getArticlesByCategories: [photostory], }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticlesByCategories, variables: { categoryNumbers: 62, limit: 1 }, }); diff --git a/client/src/pages/onboarding.jsx b/client/src/pages/onboarding.jsx index 20fc4564..d5de9398 100644 --- a/client/src/pages/onboarding.jsx +++ b/client/src/pages/onboarding.jsx @@ -1,6 +1,5 @@ import React from 'react'; import Head from 'next/head'; -import Script from 'next/script'; //Components import Onboarding from '../screens/Onboarding'; @@ -66,10 +65,6 @@ const OnBoardingPage = () => { content='Monday Morning is the Media Body of National Institute Of Technology Rourkela. Monday Morning covers all the events, issues and activities going on inside the campus. Monday morning also serves as a link between the administration and the students.' /> - ); diff --git a/client/src/pages/photostory/[...photostory].jsx b/client/src/pages/photostory/[...photostory].jsx index c3f0c353..8494a711 100644 --- a/client/src/pages/photostory/[...photostory].jsx +++ b/client/src/pages/photostory/[...photostory].jsx @@ -3,7 +3,7 @@ import { useRouter } from 'next/router'; import Head from 'next/head'; // Libraries -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; import STORES from '../../utils/getStores'; // Components @@ -177,9 +177,11 @@ export async function getStaticProps({ preview, }) { try { + const graphClient = getGraphClient(true); + const { data: { getArticleByID: photostory }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getArticleByID, variables: { id: photostoryId }, }); diff --git a/client/src/pages/portfolio/[...userId].jsx b/client/src/pages/portfolio/[...userId].jsx index 79fb10af..f6f2a793 100644 --- a/client/src/pages/portfolio/[...userId].jsx +++ b/client/src/pages/portfolio/[...userId].jsx @@ -5,7 +5,7 @@ import Portfolio from '../../screens/Portfolio'; import Marginals from '../../components/marginals/Marginals'; //Graphql -import { GraphClient } from '../../config/ApolloClient'; +import { getGraphClient } from '../../context/ApolloContextProvider'; import getUserByOldUserName from '../../graphql/queries/user/getUserByOldUserName'; import getUserById from '../../graphql/queries/user/getUserById'; import getListOfArticles from '../../graphql/queries/article/getListOfArticles'; @@ -181,13 +181,6 @@ const PortfolioPage = ({ export default PortfolioPage; -export async function getStaticPaths() { - return { - paths: [], - fallback: 'blocking', - }; -} - export async function getStaticProps({ params: { userId: [portfolioId, userSlug], @@ -196,13 +189,15 @@ export async function getStaticProps({ /**** to bring the portfolio URL in correct format - start ****/ try { + const graphClient = getGraphClient(true); + if (!userSlug && !portfolioId.match(/^[0-9a-f]{24}$/g)) { try { const { data: { getUserByOldUserName: { id, fullName }, }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getUserByOldUserName, variables: { oldUserName: portfolioId, @@ -226,7 +221,7 @@ export async function getStaticProps({ const { data: { getUserByID: user }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getUserById, variables: { getUserById: portfolioId, @@ -276,7 +271,7 @@ export async function getStaticProps({ if (articleIdList.length > 0) { const { data: { getListOfArticles: articleList }, - } = await GraphClient.query({ + } = await graphClient.query({ query: getListOfArticles, variables: { ids: articleIdList, @@ -319,3 +314,10 @@ export async function getStaticProps({ }; } } + +export async function getStaticPaths() { + return { + paths: [], + fallback: 'blocking', + }; +} diff --git a/client/src/pages/search.jsx b/client/src/pages/search.jsx index 4c37e96f..2f52cb4d 100644 --- a/client/src/pages/search.jsx +++ b/client/src/pages/search.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; // Libraries import { Container } from '@mui/material'; @@ -6,16 +6,17 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; // Components -import { GraphClient } from '../config/ApolloClient'; import ActivityIndicator from '../components/shared/ActivityIndicator'; import SearchPage from '../screens/SearchPage'; import Marginals from '../components/marginals/Marginals'; // Queries import searchArticles from '../graphql/queries/article/searchArticles'; +import { apolloContext } from '../context/ApolloContextProvider'; const Search = () => { const router = useRouter(); + const graphClient = useContext(apolloContext); const [articles, setArticles] = useState([]); const [loading, setLoading] = useState(false); @@ -24,7 +25,7 @@ const Search = () => { (async () => { const { data: { searchArticles: articleList }, - } = await GraphClient.query({ + } = await graphClient.query({ query: searchArticles, variables: { keywords: router.query.keyword, diff --git a/client/src/screens/404.js b/client/src/screens/404.js index 138afecc..2e3d1ab5 100644 --- a/client/src/screens/404.js +++ b/client/src/screens/404.js @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; import { Grid, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { ArrowForward } from '@mui/icons-material'; +import ArrowForward from '@mui/icons-material/ArrowForward'; import logo from '../assets/images/logo_mm.png'; import LINKS from '../utils/getLinks'; diff --git a/client/src/screens/Archive.jsx b/client/src/screens/Archive.jsx index 675896ad..03c224fe 100644 --- a/client/src/screens/Archive.jsx +++ b/client/src/screens/Archive.jsx @@ -1,8 +1,7 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useContext } from 'react'; import { useRouter } from 'next/router'; // libraries -import { GraphClient } from '../config/ApolloClient'; import { Container, Typography } from '@mui/material'; import Button from '@mui/material/Button'; import makeStyles from '@mui/styles/makeStyles'; @@ -17,9 +16,11 @@ import { ARCHIVES } from '../assets/placeholder/guide'; //gql import listArticlesByYearAndMonth from '../graphql/queries/article/listArticleByYearAndMonth'; +import { apolloContext } from '../context/ApolloContextProvider'; function Archive({ archiveArticles, year, month }) { const classes = useStyles(); + const graphClient = useContext(apolloContext); const { push } = useRouter(); @@ -54,7 +55,7 @@ function Archive({ archiveArticles, year, month }) { try { const { data: { listArticlesByYearAndMonth: archiveArticles }, - } = await GraphClient.query({ + } = await graphClient.query({ query: listArticlesByYearAndMonth, variables: { onlyPublished: true, diff --git a/client/src/screens/Live.js b/client/src/screens/Live.js index 69572af9..7353134b 100644 --- a/client/src/screens/Live.js +++ b/client/src/screens/Live.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; // libraries import { Typography, Container, useMediaQuery } from '@mui/material'; @@ -19,8 +19,8 @@ import theme from '../config/themes/light'; // placeholder import { LIVE } from '../assets/placeholder/live'; import getLiveByYearAndSemester from '../graphql/queries/live/getLiveByYearAndSemester'; -import { GraphClient } from '../config/ApolloClient'; import ActivityIndicator from '../components/shared/ActivityIndicator'; +import { apolloContext } from '../context/ApolloContextProvider'; function Live() { const classes = useStyles(); @@ -34,6 +34,8 @@ function Live() { const [isLoading, setLoading] = useState(true); + const graphClient = useContext(apolloContext); + const selectSessions = (season) => { setSession(season); }; @@ -53,10 +55,11 @@ function Live() { useEffect(() => { setLoading(true); const [semester, year] = activeSession.split(' '); - GraphClient.query({ - query: getLiveByYearAndSemester, - variables: { year: parseInt(year), semester: semester.toUpperCase() }, - }) + graphClient + .query({ + query: getLiveByYearAndSemester, + variables: { year: parseInt(year), semester: semester.toUpperCase() }, + }) .then((res) => { setLiveData(res.data.getLiveByYearAndSemester); setLoading(false); diff --git a/client/src/screens/Onboarding.js b/client/src/screens/Onboarding.js index 096b6f2e..9f002366 100644 --- a/client/src/screens/Onboarding.js +++ b/client/src/screens/Onboarding.js @@ -1,115 +1,179 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; +import Image from 'next/image'; +import { useRouter } from 'next/router'; + +import { Alert, Snackbar, useMediaQuery } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; -import { Typography, useMediaQuery } from '@mui/material'; -import theme from '../config/themes/light'; +//assets +import logo from '../assets/images/logo_mm.png'; +// import useToggle from '../hooks/useToggle'; +import Pagination from '../components/onboarding/Pagination'; +import NewsletterSignup from '../components/onboarding/stages/NewsletterSignup'; +import SelectTopics from '../components/onboarding/stages/SelectTopics'; +import VerifyEmail from '../components/onboarding/stages/VerifyEmail'; // Components import Welcome from '../components/onboarding/stages/Welcome'; -import VerifyEmail from '../components/onboarding/stages/VerifyEmail'; -import SelectTopics from '../components/onboarding/stages/SelectTopics'; -import NewsletterSignup from '../components/onboarding/stages/NewsletterSignup'; - +import ActivityIndicator from '../components/shared/ActivityIndicator'; +import theme from '../config/themes/light'; // Hooks import useInput from '../hooks/useInput'; -import useToggle from '../hooks/useToggle'; -import Pagination from '../components/onboarding/Pagination'; const STAGES = { - WELCOME: ['welcome-stage', 0], - INTERESTED_TOPICS: ['interested-topics', 1], - NEWSLETTER: ['newsletter-signup', 2], - VERIFY_EMAIL: ['verify-email', 3], + WELCOME: 0, + INTERESTED_TOPICS: 1, + NEWSLETTER: 2, + VERIFY_EMAIL: 3, }; function Onboarding() { const classes = useStyles(); const tabletMatches = useMediaQuery(theme.breakpoints.down('md')); + const { push, reload } = useRouter(); // Local States - const [stage, setStage] = useState(STAGES.WELCOME); + const [stage, setStage] = useState(-1); const [email, setEmail] = useInput(''); - const [isEmailVerified, toggleIsEmailVerified] = useToggle(false); - const [selectedTopics, setSelectedTopics] = useState([]); - const [newsletterEmail, setNewsletterEmail] = useInput(''); + const [isEmailVerified, toggleIsEmailVerified] = useState(false); + const [isLoading, setLoading] = useState(false); + const [snackbarData, setSnackbarData] = useState({ + message: '', + severity: 'success', + }); // Stage Change functions - // eslint-disable-next-line - const setStageToWelcome = () => setStage(STAGES.WELCOME); - const setStageToVerifyEmail = () => setStage(STAGES.VERIFY_EMAIL); + // const setStageToWelcome = () => setStage(STAGES.WELCOME); const setStageToInterestedTopics = () => setStage(STAGES.INTERESTED_TOPICS); const setStageToNewsletter = () => setStage(STAGES.NEWSLETTER); + const setStageToVerifyEmail = () => setStage(STAGES.VERIFY_EMAIL); // Local Helper Functions - const onLogin = () => console.log('Login Function Executed'); - const verifyEmail = () => console.log('Email Verify Function Executed'); - const signupNewsletter = () => console.log('Signed up for Newsletter'); - - const addSelectedTopic = (newTopic) => - setSelectedTopics((current) => [...current, newTopic]); - - const removeSelectedTopic = (topic) => - setSelectedTopics((selected) => { - return selected.filter((selectedTopic) => { - if (selectedTopic !== topic) return selectedTopic; - }); - }); + const onSignIn = () => { + setLoading(false); + if (window.location.pathname.includes('/onboarding')) { + push('/'); + } else { + reload(); + } + }; + const onSignUp = () => { + setLoading(false); + setStageToInterestedTopics(); + }; + const onSignUpNewsletter = () => { + console.log('Signed up for Newsletter'); + setLoading(false); + setStageToVerifyEmail(); + }; + const onVerifyEmail = () => { + console.log('Email Verify Function Executed'); + setLoading(false); + push('/'); + }; const renderStages = () => { - switch (stage[0]) { - case STAGES.WELCOME[0]: + switch (stage) { + case STAGES.WELCOME: return ( ); - case STAGES.INTERESTED_TOPICS[0]: + case STAGES.INTERESTED_TOPICS: return ( ); - case STAGES.NEWSLETTER[0]: + case STAGES.NEWSLETTER: return ( ); - case STAGES.VERIFY_EMAIL[0]: + case STAGES.VERIFY_EMAIL: return ( ); default: - return Loading....; + return ; } }; + useEffect(() => { + const params = new URLSearchParams(window.location.search); + + if (params.has('stage')) { + setStage(STAGES[params.get('stage')?.toUpperCase()]); + } else { + setStage(STAGES.WELCOME); + } + if (params.has('email')) { + setEmail(params.get('email')); + } + if (params.has('isEmailLink')) { + toggleIsEmailVerified(true); + } + }, []); + return (
    -
    {renderStages()}
    +
    + {renderStages()} + {isLoading && ( +
    + MM Logo +
    + )} +
    - +
    + + setSnackbarData({ message: '', severity: 'success' })} + key={`Snack: ${snackbarData.message} ${Date.now()} ${Math.random()}`} + > + setSnackbarData({ message: '', severity: 'success' })} + severity={snackbarData.severity} + variant='filled' + > + {snackbarData.message} + +
    ); } @@ -139,4 +203,10 @@ const useStyles = makeStyles((theme) => ({ width: '80%', }, }, + loading: { + margin: 'auto', + height: 'fit-content', + width: 'fit-content', + animation: 'rotation 2s linear infinite', + }, })); diff --git a/client/src/screens/admin/Admin.js b/client/src/screens/admin/Admin.js index 09859033..a76abb4a 100644 --- a/client/src/screens/admin/Admin.js +++ b/client/src/screens/admin/Admin.js @@ -11,7 +11,7 @@ import { } from '@mui/material'; // context -import { SidebarContext } from '../../context/SidebarContext'; +import { SidebarContext } from '../../context/SidebarContextProvider'; // components import Header from '../../components/admin/Header'; diff --git a/client/src/screens/admin/Browse.js b/client/src/screens/admin/Browse.js index 0addf17a..231c7a16 100644 --- a/client/src/screens/admin/Browse.js +++ b/client/src/screens/admin/Browse.js @@ -20,15 +20,13 @@ import { IconButton, Tooltip, } from '@mui/material'; -import { - Delete, - FilterList, - Edit, - Publish, - Archive, - Comment, - CircleSharp, -} from '@mui/icons-material'; +import Delete from '@mui/icons-material/Delete'; +import FilterList from '@mui/icons-material/FilterList'; +import Edit from '@mui/icons-material/Edit'; +import Publish from '@mui/icons-material/Publish'; +import Archive from '@mui/icons-material/Archive'; +import Comment from '@mui/icons-material/Comment'; +import CircleSharp from '@mui/icons-material/CircleSharp'; import { visuallyHidden } from '@mui/utils'; function createData(name, calories, fat, carbs, protein, status) { diff --git a/client/src/store/actions/index.js b/client/src/store/actions/index.js deleted file mode 100644 index 1c50bda1..00000000 --- a/client/src/store/actions/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// For demo purposes the types are imported here. -// For actual project, create different action files with the format -// action_name.action.js. -// Import all actions in this folder and export them from here itself. -import { DEMO } from './types'; diff --git a/client/src/store/actions/types.js b/client/src/store/actions/types.js deleted file mode 100644 index a1f73a5b..00000000 --- a/client/src/store/actions/types.js +++ /dev/null @@ -1,5 +0,0 @@ -// Create all actions types in the form a nested object - -export const DEMO = { - TYPE1: 'TYPE1', -}; diff --git a/client/src/store/reducers/index.js b/client/src/store/reducers/index.js deleted file mode 100644 index 2d1ec238..00000000 --- a/client/src/store/reducers/index.js +++ /dev/null @@ -1 +0,0 @@ -export default () => {}; diff --git a/client/src/utils/getAccess.js b/client/src/utils/getAccess.js index 7d7ebecd..112793c0 100644 --- a/client/src/utils/getAccess.js +++ b/client/src/utils/getAccess.js @@ -11,7 +11,7 @@ export default async function getAccess(ctx, permissions) { mode: 'cors', cache: 'no-cache', headers: { - authorization: cookies.firebaseToken, + authorization: cookies?.firebaseToken, }, }, ); diff --git a/client/src/utils/getAdminRoutes.js b/client/src/utils/getAdminRoutes.js index 3fc0d4f6..624b7d7a 100644 --- a/client/src/utils/getAdminRoutes.js +++ b/client/src/utils/getAdminRoutes.js @@ -1,5 +1,5 @@ // library -import { Create } from '@mui/icons-material'; +import Create from '@mui/icons-material/Create'; const admin = { id: 'admin', diff --git a/client/yarn.lock b/client/yarn.lock index 1bf2e46b..b2aadaf7 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -6809,11 +6809,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rifm@^0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.12.1.tgz#8fa77f45b7f1cda2a0068787ac821f0593967ac4" - integrity sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg== - rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"