├── .gitignore ├── webpack.config.js ├── .env.example ├── netlify.toml ├── public ├── weebook.png └── index.html ├── src ├── index.css ├── redux │ ├── reducers │ │ ├── index.js │ │ ├── auth.js │ │ ├── posts.js │ │ └── user.js │ ├── constants │ │ └── actionConstants.js │ └── actions │ │ └── posts.js ├── components │ ├── Home │ │ ├── FloatingActBtnStyles.js │ │ ├── FloatingActBtn.js │ │ ├── HomeStyles.js │ │ └── Home.js │ ├── Footer │ │ ├── FooterStyles.js │ │ └── Footer.js │ ├── Feed │ │ ├── PostsFeedStyles.js │ │ ├── IndividualPostStyles.js │ │ ├── PostsFeed.js │ │ └── IndividualPost.js │ ├── MdEditor │ │ ├── ModalContainer.js │ │ ├── ModalContainerStyles.js │ │ ├── ModalContentStyles.js │ │ └── ModalContent.js │ ├── Navbar │ │ ├── NavStyles.js │ │ ├── Drawer.js │ │ └── Navbar.js │ └── Auth │ │ ├── AuthStyles.js │ │ └── Auth.js ├── index.js ├── Apis │ └── Api.js ├── Hooks │ └── WindowSize.js └── App.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_CLIENT_ID = 'Your Goolge Client Id' -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 -------------------------------------------------------------------------------- /public/weebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikhilsourav/weebook-client/HEAD/public/weebook.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | } 6 | html { 7 | scroll-behavior: smooth; 8 | height: 100vh; 9 | } 10 | body { 11 | font-family: 'Roboto', Arial, Helvetica, sans-serif; 12 | } 13 | -------------------------------------------------------------------------------- /src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | // Redux import 2 | import { combineReducers } from 'redux'; 3 | 4 | // reducers import 5 | import posts from './posts'; 6 | import auth from './auth'; 7 | import user from './user'; 8 | 9 | // all reducers are combined and exported to root index.js 10 | export default combineReducers({ posts, auth, user }); 11 | -------------------------------------------------------------------------------- /src/components/Home/FloatingActBtnStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | Container: { 5 | position: 'fixed', 6 | bottom: '50px', 7 | right: '50px', 8 | display: 'none', 9 | [theme.breakpoints.down(500)]: { 10 | display: 'flex', 11 | }, 12 | }, 13 | })); 14 | -------------------------------------------------------------------------------- /src/components/Footer/FooterStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | BottomGreet: { 5 | width: 'fit-content', 6 | display: 'block', 7 | margin: 'auto', 8 | ...theme.typography.button, 9 | backgroundColor: theme.palette.background.main, 10 | padding: theme.spacing(2), 11 | }, 12 | })); 13 | -------------------------------------------------------------------------------- /src/redux/constants/actionConstants.js: -------------------------------------------------------------------------------- 1 | export const CREATE = 'CREATE'; 2 | export const UPDATE = 'UPDATE'; 3 | export const DELETE = 'DELETE'; 4 | export const FETCH_ALL = 'FETCH_ALL'; 5 | 6 | export const AUTH = 'AUTH'; 7 | export const LOGOUT = 'LOGOUT'; 8 | 9 | export const SET_CURRENT_USER_ID = 'SET_CURRENT_USER_ID '; 10 | export const INITIATE_POST_EDIT = 'INITIATE_POST_EDIT'; 11 | export const POST_CLICKED = 'POST_CLICKED'; 12 | export const FAB_CLICKED = 'FAB_CLICKED'; 13 | -------------------------------------------------------------------------------- /src/components/Feed/PostsFeedStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | Posts: { 5 | backgroundColor: theme.palette.primary.main, 6 | padding: '15px', 7 | [theme.breakpoints.down('sm')]: { 8 | paddingLeft: theme.spacing(1), 9 | paddingRight: theme.spacing(1), 10 | }, 11 | }, 12 | Skeleton: { 13 | marginBottom: '40px', 14 | }, 15 | SkeletonItem: { 16 | marginTop: '10px', 17 | }, 18 | })); 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | import { Provider } from 'react-redux'; 7 | import { configureStore } from '@reduxjs/toolkit'; 8 | import reducers from './redux/reducers/'; 9 | 10 | // create global store 11 | const store = configureStore({ reducer: reducers }); 12 | 13 | ReactDOM.render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | -------------------------------------------------------------------------------- /src/redux/reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { AUTH, LOGOUT } from '../constants/actionConstants'; 2 | 3 | const authReducer = (state = { authData: null }, action) => { 4 | switch (action.type) { 5 | case AUTH: 6 | localStorage.setItem('profile', JSON.stringify({ ...action?.data })); 7 | return { ...state, authData: action?.data }; 8 | case LOGOUT: 9 | localStorage.clear(); 10 | return { ...state, authData: null }; 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export default authReducer; 17 | -------------------------------------------------------------------------------- /src/components/MdEditor/ModalContainer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Container for ModalContent 3 | */ 4 | import { Paper, Container } from '@material-ui/core'; 5 | import useStyles from './ModalContainerStyles'; 6 | import Form from './ModalContent'; 7 | 8 | const ModalContainer = () => { 9 | const classes = useStyles(); 10 | 11 | return ( 12 | 13 | 14 |
15 | 16 | 17 | ); 18 | }; 19 | 20 | export default ModalContainer; 21 | -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import { Paper, Typography } from '@material-ui/core'; 2 | import { useLocation } from 'react-router-dom'; 3 | import useStyles from './FooterStyles'; 4 | 5 | const Footer = () => { 6 | const classes = useStyles(); 7 | const location = useLocation(); 8 | const greetingText = 9 | location.pathname === '/auth' ? 'Hey there friendly citizen!' : 'Wow! You scroll too much!'; 10 | 11 | return ( 12 | 13 | 14 | {greetingText} 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default Footer; 21 | -------------------------------------------------------------------------------- /src/redux/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // import const action types 2 | import { FETCH_ALL, DELETE, CREATE, UPDATE } from '../constants/actionConstants'; 3 | 4 | const postReducers = (posts = [], action) => { 5 | switch (action.type) { 6 | case FETCH_ALL: 7 | return action.payload; 8 | case CREATE: 9 | return [action.payload, ...posts]; 10 | case UPDATE: 11 | return posts.map((post) => (post._id === action.payload._id ? action.payload : post)); 12 | case DELETE: 13 | return posts.filter((post) => post._id !== action.payload); 14 | default: 15 | return posts; 16 | } 17 | }; 18 | 19 | export default postReducers; 20 | -------------------------------------------------------------------------------- /src/components/Navbar/NavStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | Heading: { 5 | flexGrow: 1, 6 | fontFamily: 'Roboto', 7 | color: 'rgba(255,255,250,1)', 8 | }, 9 | Profile: { 10 | width: theme.spacing(3.5), 11 | height: theme.spacing(3.5), 12 | }, 13 | NavItem: { 14 | display: 'flex', 15 | justifyContent: 'space-between', 16 | alignItems: 'center', 17 | width: '100%', 18 | minWidth: '160px', 19 | minHeight: '50px', 20 | }, 21 | Modal: { 22 | display: 'flex', 23 | justifyContent: 'space-between', 24 | alignItems: 'center', 25 | }, 26 | })); 27 | -------------------------------------------------------------------------------- /src/components/MdEditor/ModalContainerStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | ModalContainer: { 5 | height: 'max(81vh, 550px)', 6 | width: '90%', 7 | padding: '15px', 8 | backgroundColor: theme.palette.primary.main, 9 | [theme.breakpoints.down('xs')]: { 10 | marginTop: theme.spacing(2), 11 | width: '100%', 12 | height: '550px', 13 | }, 14 | [theme.breakpoints.down('sm')]: { 15 | paddingLeft: theme.spacing(1), 16 | paddingRight: theme.spacing(1), 17 | }, 18 | position: 'relative', 19 | }, 20 | Paper: { 21 | height: '100%', 22 | }, 23 | })); 24 | -------------------------------------------------------------------------------- /src/components/Home/FloatingActBtn.js: -------------------------------------------------------------------------------- 1 | import { Fab } from '@material-ui/core'; 2 | import AddIcon from '@material-ui/icons/Add'; 3 | import useStyles from './FloatingActBtnStyles'; 4 | import { useDispatch } from 'react-redux'; 5 | import { FAB_CLICKED } from '../../redux/constants/actionConstants'; 6 | 7 | const FloatingActionButton = () => { 8 | const classes = useStyles(); 9 | const dispatch = useDispatch(); 10 | 11 | const handleClick = () => { 12 | dispatch({ type: FAB_CLICKED, payload: true }); 13 | }; 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default FloatingActionButton; 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | weebook 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/Home/HomeStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | Container: { 5 | height: '100%', 6 | outline: 'none', 7 | position: 'relative', 8 | maxWidth: '1100px', 9 | overflowX: 'hidden', 10 | [theme.breakpoints.down('xs')]: { 11 | padding: '0', 12 | }, 13 | }, 14 | GridContainer: { 15 | flexDirection: 'row', 16 | justifyContent: 'center', 17 | alignItems: 'flex-start', 18 | margin: '20px 0', 19 | padding: theme.spacing(1), 20 | [theme.breakpoints.down('xs')]: { 21 | flexDirection: 'column-reverse', 22 | alignItems: 'stretch', 23 | flexGrow: 1, 24 | }, 25 | }, 26 | })); 27 | -------------------------------------------------------------------------------- /src/Apis/Api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const API = axios.create({ baseURL: 'https://weebook-ager.onrender.com/' }); 4 | 5 | // send req with headers 6 | API.interceptors.request.use((req) => { 7 | if (localStorage.getItem('profile')) { 8 | req.headers.authorization = `Bearer ${JSON.parse(localStorage.getItem('profile')).token}`; 9 | } 10 | return req; 11 | }); 12 | 13 | export const getPosts = () => API.get('/posts'); 14 | export const createPost = (newPost) => API.post('/posts', newPost); 15 | export const updatePost = (id, updatedPost) => API.patch(`/posts/${id}`, updatedPost); 16 | export const deletePost = (id) => API.delete(`/posts/${id}`); 17 | export const likePost = (id) => API.patch(`/posts/${id}/likePost`); 18 | -------------------------------------------------------------------------------- /src/components/Auth/AuthStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | AuthBody: { 5 | display: 'flex', 6 | alignItems: 'center', 7 | justifyContent: 'center', 8 | height: '100vh', 9 | }, 10 | paper: { 11 | width: '100%', 12 | height: 'auto', 13 | marginTop: theme.spacing(8), 14 | display: 'flex', 15 | flexDirection: 'column', 16 | alignItems: 'center', 17 | padding: theme.spacing(2), 18 | }, 19 | avatar: { 20 | margin: theme.spacing(1), 21 | backgroundColor: theme.palette.primary.main, 22 | }, 23 | form: { 24 | width: 'fit-content', 25 | marginTop: theme.spacing(4), 26 | marginBottom: theme.spacing(4), 27 | marginLeft: 'auto', 28 | marginRight: 'auto', 29 | }, 30 | })); 31 | -------------------------------------------------------------------------------- /src/components/MdEditor/ModalContentStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | root: { 5 | '& .MuiTextField-root': { 6 | marginBottom: theme.spacing(2), 7 | marginLeft: theme.spacing(0.7), 8 | marginRight: theme.spacing(0.7), 9 | }, 10 | }, 11 | Form: { 12 | display: 'flex', 13 | flexWrap: 'wrap', 14 | justifyContent: 'center', 15 | }, 16 | FormHeading: { 17 | margin: '15px 0', 18 | }, 19 | Clear: { 20 | position: 'absolute', 21 | top: '22px', 22 | right: '25px', 23 | }, 24 | ButtonSubmit: { 25 | marginBottom: 10, 26 | }, 27 | SignIn: { 28 | height: '100%', 29 | padding: '30px', 30 | display: 'flex', 31 | alignItems: 'center', 32 | justifyContent: 'center', 33 | }, 34 | })); 35 | -------------------------------------------------------------------------------- /src/components/Home/Home.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { Container, Grid } from '@material-ui/core'; 3 | import { useDispatch } from 'react-redux'; 4 | 5 | import FloatingActBtn from './FloatingActBtn'; 6 | import useStyles from './HomeStyles'; 7 | import Posts from '../Feed/PostsFeed'; 8 | import { getPosts } from '../../redux/actions/posts'; 9 | 10 | const Home = () => { 11 | const classes = useStyles(); 12 | const dispatch = useDispatch(); 13 | 14 | useEffect(() => { 15 | dispatch(getPosts()); 16 | }, [dispatch]); 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default Home; 31 | -------------------------------------------------------------------------------- /src/redux/reducers/user.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_CURRENT_USER_ID, 3 | INITIATE_POST_EDIT, 4 | POST_CLICKED, 5 | FAB_CLICKED, 6 | } from '../constants/actionConstants'; 7 | 8 | const initialUserState = { 9 | currentUserId: null, 10 | hasSelectedEditMode: false, 11 | hasClickedPostBtn: false, 12 | hasClickedFab: false, 13 | }; 14 | 15 | const userReducers = (state = initialUserState, action) => { 16 | switch (action.type) { 17 | case SET_CURRENT_USER_ID: { 18 | return { ...state, currentUserId: action.payload }; 19 | } 20 | case INITIATE_POST_EDIT: { 21 | return { ...state, hasSelectedEditMode: action.payload }; 22 | } 23 | case POST_CLICKED: { 24 | return { ...state, hasClickedPostBtn: action.payload }; 25 | } 26 | case FAB_CLICKED: { 27 | return { ...state, hasClickedFab: action.payload }; 28 | } 29 | default: 30 | return state; 31 | } 32 | }; 33 | 34 | export default userReducers; 35 | -------------------------------------------------------------------------------- /src/Hooks/WindowSize.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | // Get window's dimension 4 | const useWindowDimensions = () => { 5 | const getWindowDimensions = () => { 6 | const { innerWidth: width, innerHeight: height } = window; 7 | return { width, height }; 8 | }; 9 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions()); 10 | useEffect(() => { 11 | function handleResize() { 12 | setWindowDimensions(getWindowDimensions()); 13 | } 14 | window.addEventListener('resize', handleResize); 15 | return () => window.removeEventListener('resize', handleResize); 16 | }, []); 17 | 18 | return windowDimensions; 19 | }; 20 | 21 | // Resize textfield dynamically using useCalcRows 22 | const useCalcRows = () => { 23 | const { width: windowWidth, height: windowHeight } = useWindowDimensions(); 24 | 25 | if (windowHeight > 760) { 26 | if (windowWidth < 600) { 27 | return { minRows: 14, maxRows: 14 }; 28 | } else if (windowWidth < 960) { 29 | return { minRows: 27, maxRows: 27 }; 30 | } 31 | return { minRows: 27, maxRows: 27 }; 32 | } else { 33 | if (windowWidth < 600) { 34 | return { minRows: 15, maxRows: 15 }; 35 | } else if (windowWidth < 960) { 36 | return { minRows: 18, maxRows: 18 }; 37 | } 38 | return { minRows: 18, maxRows: 18 }; 39 | } 40 | }; 41 | 42 | export { useCalcRows, useWindowDimensions }; 43 | -------------------------------------------------------------------------------- /src/components/Feed/IndividualPostStyles.js: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core/styles'; 2 | 3 | export default makeStyles((theme) => ({ 4 | root: { 5 | height: '100%', 6 | padding: theme.spacing(1), 7 | marginBottom: theme.spacing(2), 8 | position: 'relative', 9 | '&:hover': { 10 | boxShadow: '7px 7px 15px -4px rgba(0,0,0,0.5)', 11 | }, 12 | }, 13 | HeadWrapper: { 14 | display: 'flex', 15 | justifyContent: 'space-between', 16 | alignItems: 'center', 17 | position: 'relative', 18 | }, 19 | Info: { 20 | display: 'flex', 21 | justifyContent: 'flex-start', 22 | [theme.breakpoints.down('sm')]: { 23 | flexDirection: 'column', 24 | }, 25 | }, 26 | Name: { 27 | fontSize: 14, 28 | color: theme.palette.text.secondary, 29 | marginRight: '20px', 30 | }, 31 | CreatedAt: { 32 | fontSize: 14, 33 | color: theme.palette.text.secondary, 34 | [theme.breakpoints.down('sm')]: { 35 | marginTop: '10px', 36 | }, 37 | }, 38 | Edit: { 39 | [theme.breakpoints.down('sm')]: { 40 | position: 'absolute', 41 | right: '-12px', 42 | top: '50%', 43 | transform: 'translateY(-50%)', 44 | }, 45 | }, 46 | Title: { 47 | margin: '25px 0', 48 | fontWeight: 'bold', 49 | }, 50 | Content: { 51 | wordSpacing: '2px', 52 | }, 53 | Actions: { 54 | display: 'flex', 55 | justifyContent: 'space-between', 56 | }, 57 | })); 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.3", 7 | "@material-ui/icons": "^4.11.2", 8 | "@material-ui/lab": "^4.0.0-alpha.57", 9 | "@react-oauth/google": "^0.12.1", 10 | "@reduxjs/toolkit": "^1.9.7", 11 | "@testing-library/jest-dom": "^5.11.9", 12 | "@testing-library/react": "^11.2.5", 13 | "@testing-library/user-event": "^12.7.0", 14 | "axios": "^1.6.2", 15 | "dotenv": "^8.6.0", 16 | "jwt-decode": "^3.1.2", 17 | "moment": "^2.29.1", 18 | "react": "^17.0.1", 19 | "react-dom": "^17.0.1", 20 | "react-redux": "^8.1.3", 21 | "react-router-dom": "^5.2.0", 22 | "react-scripts": "^5.0.1", 23 | "redux-thunk": "^2.4.2", 24 | "web-vitals": "^1.1.0" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/Feed/PostsFeed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Container for IndividualPost 3 | */ 4 | import { Paper, Typography } from '@material-ui/core'; 5 | import { useSelector } from 'react-redux'; 6 | import Skeleton from '@material-ui/lab/Skeleton'; 7 | 8 | import Post from './IndividualPost'; 9 | import useStyles from './PostsFeedStyles'; 10 | 11 | const SkeletonLoader = () => { 12 | const classes = useStyles(); 13 | 14 | const renderSkeletonItem = () => ( 15 | <> 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | 24 | return ( 25 |
26 | {[1, 2, 3].map((key) => ( 27 | 28 | {renderSkeletonItem()} 29 | 30 | ))} 31 |
32 | ); 33 | }; 34 | 35 | const Posts = () => { 36 | const classes = useStyles(); 37 | const posts = useSelector((state) => state.posts); 38 | 39 | return !posts.length ? ( 40 | 41 | ) : ( 42 | 43 | {posts.map((post) => ( 44 | 45 | ))} 46 | 47 | ); 48 | }; 49 | 50 | export default Posts; 51 | -------------------------------------------------------------------------------- /src/redux/actions/posts.js: -------------------------------------------------------------------------------- 1 | import * as api from '../../Apis/Api.js'; // backend services 2 | import { FETCH_ALL, DELETE, UPDATE, CREATE } from '../constants/actionConstants'; 3 | 4 | // Action creators 5 | export const getPosts = () => async (dispatch) => { 6 | try { 7 | const { data } = await api.getPosts(); 8 | dispatch({ type: FETCH_ALL, payload: data }); 9 | } catch (error) { 10 | console.error('Error in getPosts action creator:', error); 11 | } 12 | }; 13 | 14 | export const createPost = (post) => async (dispatch) => { 15 | try { 16 | const { data } = await api.createPost(post); 17 | dispatch({ type: CREATE, payload: data }); 18 | } catch (error) { 19 | console.error('Error in createPost action creator:', error); 20 | } 21 | }; 22 | 23 | export const updatePost = (id, post) => async (dispatch) => { 24 | try { 25 | const { data } = await api.updatePost(id, post); 26 | dispatch({ type: UPDATE, payload: data }); 27 | } catch (error) { 28 | console.error('Error in updatePost action creator:', error); 29 | } 30 | }; 31 | 32 | export const deletePost = (id) => async (dispatch) => { 33 | try { 34 | await api.deletePost(id); 35 | dispatch({ type: DELETE, payload: id }); 36 | } catch (error) { 37 | console.error('Error in deletePost action creator:', error); 38 | } 39 | }; 40 | 41 | export const likePost = (id) => async (dispatch) => { 42 | try { 43 | const { data } = await api.likePost(id); 44 | dispatch({ type: UPDATE, payload: data }); 45 | } catch (error) { 46 | console.error('Error in likePost action creator:', error); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/components/Auth/Auth.js: -------------------------------------------------------------------------------- 1 | import jwt_decode from 'jwt-decode'; 2 | import LockOutlined from '@material-ui/icons/LockOutlined'; 3 | import { useHistory } from 'react-router-dom'; 4 | import { Container, Avatar, Typography, Paper, Grid } from '@material-ui/core'; 5 | import { GoogleLogin } from '@react-oauth/google'; 6 | import { useDispatch } from 'react-redux'; 7 | 8 | import useStyles from './AuthStyles'; 9 | import { AUTH } from '../../redux/constants/actionConstants'; 10 | 11 | const Auth = () => { 12 | const classes = useStyles(); 13 | const dispatch = useDispatch(); 14 | const history = useHistory(); 15 | 16 | // successful login 17 | const googleSuccess = (res) => { 18 | const token = res?.credential; 19 | const result = jwt_decode(token); 20 | try { 21 | dispatch({ type: AUTH, data: { result, token } }); 22 | history.push('/'); 23 | } catch (error) { 24 | console.log(error); 25 | } 26 | }; 27 | 28 | // login failure 29 | const googleFailure = () => { 30 | console.log(`Google sign in was unsuccessful`); 31 | }; 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | Sign in 40 | 41 | 42 | 47 | 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default Auth; 55 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { BrowserRouter, Switch, Route } from 'react-router-dom'; 3 | import { GoogleOAuthProvider } from '@react-oauth/google'; 4 | import { Paper, ThemeProvider, createTheme } from '@material-ui/core'; 5 | import blue from '@material-ui/core/colors/blue'; 6 | 7 | import Navbar from './components/Navbar/Navbar'; 8 | import Home from './components/Home/Home'; 9 | import Auth from './components/Auth/Auth'; 10 | import Footer from './components/Footer/Footer'; 11 | 12 | // Greetings 13 | const greetVisitor = () => { 14 | console.log( 15 | '%c Thanks for visiting!', 16 | 'font-weight: bold; font-size: 20px; background-color: #2196f3; border-radius: 5px; padding: 10px; text-shadow: 2px 2px rgba(0,0,0,0.5)' 17 | ); 18 | }; 19 | 20 | // Theme settings 21 | const useTheme = () => { 22 | const [themeMode, setThemeMode] = useState('dark'); 23 | const theme = createTheme({ palette: { primary: blue, type: themeMode } }); 24 | const toggleLightMode = () => setThemeMode('light'); 25 | const toggleDarkMode = () => setThemeMode('dark'); 26 | return { themeMode, theme, toggleLightMode, toggleDarkMode }; 27 | }; 28 | 29 | const App = () => { 30 | greetVisitor(); 31 | const { themeMode, theme, toggleLightMode, toggleDarkMode } = useTheme(); 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |