├── src
├── styles
│ ├── components
│ │ ├── About.js
│ │ ├── generalStyles.js
│ │ ├── notFoundPageStyles.js
│ │ ├── menubarStyles.js
│ │ ├── pageStyles.js
│ │ ├── animations.js
│ │ ├── windowStyles.js
│ │ ├── settingStyles.js
│ │ ├── commentStyles.js
│ │ ├── postStyles.js
│ │ ├── headerStyles.js
│ │ ├── tabStyles.js
│ │ ├── exploreStyles.js
│ │ ├── notificationbarStyles.js
│ │ ├── inboxStyles.js
│ │ ├── followingtabStyles.js
│ │ ├── redditorStyles.js
│ │ ├── userStyles.js
│ │ ├── viewPostStyles.js
│ │ ├── profileStyles.js
│ │ └── searchbarStyles.js
│ └── base
│ │ ├── colors.js
│ │ └── base.js
├── setupTests.js
├── components
│ ├── About.js
│ ├── NotFound.js
│ ├── OptTab.js
│ ├── App.js
│ ├── Trophies.js
│ ├── Settings.js
│ ├── Dashboard.js
│ ├── Comments.js
│ ├── Menubar.js
│ ├── RenderSubs.js
│ ├── FollowingTab.js
│ ├── Redditor.js
│ ├── FetchToken.js
│ ├── ContentBox.js
│ ├── PostCard.js
│ ├── Inbox.js
│ ├── Notificationbar.js
│ ├── User.js
│ ├── Explore.js
│ ├── Notifications.js
│ ├── Header.js
│ ├── Searchbar.js
│ ├── Profile.js
│ ├── SubredditProfile.js
│ ├── LoadPost.js
│ └── ViewPost.js
├── store
│ └── index.js
├── reducers
│ ├── index.js
│ ├── settingsReducer.js
│ ├── authenticationReducer.js
│ ├── subredditReducer.js
│ └── postReducer.js
├── router
│ ├── PrivateRoute.js
│ └── AppRouter.js
├── actions
│ ├── action-types.js
│ ├── api-calls.js
│ └── index.js
├── index.js
└── serviceWorker.js
├── public
├── favicon.png
├── robots.txt
├── images
│ ├── dm.png
│ ├── home.png
│ ├── icon.png
│ ├── like.png
│ ├── chakra.png
│ ├── comment.png
│ ├── compas.png
│ ├── growth.png
│ ├── trophy.png
│ ├── upvote.png
│ ├── Astronaut.png
│ ├── dm-active.png
│ ├── dm-light.png
│ ├── home-light.png
│ ├── left-arrow.png
│ ├── like-light.png
│ ├── settings.png
│ ├── compas-light.png
│ ├── defaulticon.png
│ ├── trophy-light.png
│ ├── comment-light.png
│ ├── downvote-active.png
│ ├── settings-light.png
│ ├── space-discovery.png
│ ├── upvote-active.png
│ └── left-arrow-light.png
├── loaders
│ └── spinner.gif
├── index.html
└── svg
│ └── send.svg
├── .gitignore
├── server
└── server.js
├── README.md
└── package.json
/src/styles/components/About.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-componenets";
2 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/favicon.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/images/dm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/dm.png
--------------------------------------------------------------------------------
/public/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/home.png
--------------------------------------------------------------------------------
/public/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/icon.png
--------------------------------------------------------------------------------
/public/images/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/like.png
--------------------------------------------------------------------------------
/public/images/chakra.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/chakra.png
--------------------------------------------------------------------------------
/public/images/comment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/comment.png
--------------------------------------------------------------------------------
/public/images/compas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/compas.png
--------------------------------------------------------------------------------
/public/images/growth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/growth.png
--------------------------------------------------------------------------------
/public/images/trophy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/trophy.png
--------------------------------------------------------------------------------
/public/images/upvote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/upvote.png
--------------------------------------------------------------------------------
/public/images/Astronaut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/Astronaut.png
--------------------------------------------------------------------------------
/public/images/dm-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/dm-active.png
--------------------------------------------------------------------------------
/public/images/dm-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/dm-light.png
--------------------------------------------------------------------------------
/public/images/home-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/home-light.png
--------------------------------------------------------------------------------
/public/images/left-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/left-arrow.png
--------------------------------------------------------------------------------
/public/images/like-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/like-light.png
--------------------------------------------------------------------------------
/public/images/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/settings.png
--------------------------------------------------------------------------------
/public/loaders/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/loaders/spinner.gif
--------------------------------------------------------------------------------
/public/images/compas-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/compas-light.png
--------------------------------------------------------------------------------
/public/images/defaulticon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/defaulticon.png
--------------------------------------------------------------------------------
/public/images/trophy-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/trophy-light.png
--------------------------------------------------------------------------------
/public/images/comment-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/comment-light.png
--------------------------------------------------------------------------------
/public/images/downvote-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/downvote-active.png
--------------------------------------------------------------------------------
/public/images/settings-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/settings-light.png
--------------------------------------------------------------------------------
/public/images/space-discovery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/space-discovery.png
--------------------------------------------------------------------------------
/public/images/upvote-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/upvote-active.png
--------------------------------------------------------------------------------
/public/images/left-arrow-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaesharma/redditfornormies/HEAD/public/images/left-arrow-light.png
--------------------------------------------------------------------------------
/src/styles/components/generalStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledDiv = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | `;
7 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/src/styles/base/colors.js:
--------------------------------------------------------------------------------
1 | export const colors = {
2 | gray1: "#f2f2f2",
3 | gray2: "#e2e2e2",
4 | gray3: "#b2b2b2",
5 | gray4: "#aaa",
6 | gray5: "#a2a2a2",
7 | gray6: "#999",
8 | gray7: "#929292",
9 | red: "#d9310b",
10 | blue: "#0095f6",
11 | dark: "black",
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/About.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyledCanvas } from "../styles/components/tabStyles";
3 | import { StyledHeader } from "../styles/components/headerStyles";
4 |
5 | const About = () => (
6 |
7 | About Page
8 |
9 | );
10 |
11 | export default About;
12 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from "redux";
2 | import thunk from "redux-thunk";
3 | import rootReducer from "../reducers";
4 |
5 | const composeEnhancers = compose;
6 |
7 | const store = createStore(
8 | rootReducer,
9 | composeEnhancers(applyMiddleware(thunk))
10 | );
11 |
12 | export default store;
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 |
26 | .env.development
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import postReducer from "./postReducer";
2 | import subredditReducer from "./subredditReducer";
3 | import authenticationReducer from "./authenticationReducer";
4 | import settingsReducer from "./settingsReducer";
5 | import { combineReducers } from "redux";
6 |
7 | const rootReducer = combineReducers({
8 | authenticationReducer,
9 | postReducer,
10 | subredditReducer,
11 | settingsReducer,
12 | });
13 |
14 | export default rootReducer;
15 |
--------------------------------------------------------------------------------
/src/styles/components/notFoundPageStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledPageCover = styled.div`
4 | width: 100vw;
5 | height: 100vh;
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | text-align: center;
11 | & > h1 {
12 | margin: 0;
13 | }
14 | & > h2 {
15 | color: gray;
16 | }
17 | `;
18 |
19 | export const StyledImg = styled.img`
20 | width: 25rem;
21 | height: 25rem;
22 | `;
23 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const port=process.env.PORT || 3000;
4 | const app = express();
5 | const root = require('path').join(__dirname, '..', 'build');
6 | const cors = require('cors');
7 |
8 | app.use(cors());
9 | app.options('*', cors());
10 | app.use(express.static(root));
11 | app.get('*', function(req, res) {
12 | res.sendFile(path.join(root,'index.html'));
13 | });
14 | app.listen(port,()=>{
15 | console.log('server is up');
16 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | The demonstration of this web-app is deployed at https://redditfornormies.herokuapp.com
2 |
3 | 1. Run this app locally by running `npm install` in project root directory to install dependencies,
4 |
5 | 2. Run `npm run build` to build react scripts.
6 |
7 | 3. Choose one of the following according to your environment:-
8 |
9 | a. `npm run devserver` to run local development server.
10 |
11 | b. `npm run start` to run production server.
12 |
13 | Feel free to PR for additional features & bug fixes. :D
--------------------------------------------------------------------------------
/src/reducers/settingsReducer.js:
--------------------------------------------------------------------------------
1 | import { NIGHTMODE } from "../actions/action-types";
2 |
3 | const initialState = {
4 | nightmode: false,
5 | };
6 |
7 | const settingsReducer = (state = initialState, action) => {
8 | switch (action.type) {
9 | case NIGHTMODE:
10 | window.localStorage.setItem("nightMode", action.payload.nightmode);
11 | return {
12 | ...state,
13 | nightmode: action.payload.nightmode,
14 | };
15 | default:
16 | return state;
17 | }
18 | };
19 |
20 | export default settingsReducer;
21 |
--------------------------------------------------------------------------------
/src/styles/components/menubarStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledMenubar = styled.div`
4 | display: none;
5 | bottom: 0%;
6 | width: 100%;
7 | position: fixed;
8 | background: ${({ theme }) => theme.bg};
9 | justify-content: space-around;
10 | height: 2.8rem;
11 | align-items: center;
12 | border: none;
13 | border-top: 1px ${({ theme }) => theme.themeborder} solid;
14 | z-index: 6;
15 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
16 | display: flex;
17 | }
18 | `;
19 |
--------------------------------------------------------------------------------
/src/components/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "./Header";
3 | import {
4 | StyledPageCover,
5 | StyledImg,
6 | } from "../styles/components/notFoundPageStyles";
7 |
8 | const NotFound = () => {
9 | return (
10 |
11 |
12 |
13 |
14 | 404
15 |
16 | The page you are looking for doesn't exist or has been
17 | moved.
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default NotFound;
25 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 | reddit for normies
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/styles/base/base.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 |
3 | const GlobalStyles = createGlobalStyle`
4 | *{
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | color: ${(props) => props.theme.color};
10 | background: ${(props) => props.theme.bodybg};
11 | margin: 0;
12 | font-family: 'Nunito Sans', sans-serif;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 | a{
17 | color: ${({ theme }) => theme.colors.gray5};
18 | cursor: pointer;
19 | }
20 | a:visited{
21 | color: #06205c;
22 | }
23 | iframe{
24 | border: none;
25 | margin: 0;
26 | padding: 0;
27 | }
28 | `;
29 |
30 | export default GlobalStyles;
31 |
--------------------------------------------------------------------------------
/src/router/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { Route, Redirect } from "react-router-dom";
4 | import Header from "../components/Header";
5 |
6 | export const PrivateRoute = ({
7 | isAuthenticated,
8 | component: Component,
9 | ...rest
10 | }) => (
11 |
14 | isAuthenticated ? (
15 |
16 |
17 |
18 | ) : (
19 |
20 | )
21 | }
22 | />
23 | );
24 |
25 | const mapStateToProps = (state) => ({
26 | isAuthenticated: state.authenticationReducer.authenticated,
27 | });
28 |
29 | export default connect(mapStateToProps)(PrivateRoute);
30 |
--------------------------------------------------------------------------------
/src/actions/action-types.js:
--------------------------------------------------------------------------------
1 | export const ADD_SUBREDDIT = "ADD_SUBREDDIT";
2 | export const ADD_SUBREDDITS = "ADD_SUBREDDITS";
3 | export const DESELECT_SUBREDDIT = "DESELECT_SUBREDDIT";
4 | export const DOWNVOTE = "DOWNVOTE";
5 | export const HOMEPAGE = "HOMEPAGE";
6 | export const HOMEPAGEPOST_UPVOTE = "HOMEPAGEPOST_UPVOTE";
7 | export const HOMEPAGEPOST_DOWNVOTE = "HOMEPAGEPOST_DOWNVOTE";
8 | export const LOAD_HOMEPAGE = "LOAD_HOMEPAGE";
9 | export const LOGGED_IN = "LOGGED_IN";
10 | export const LOGOUT = "LOGOUT";
11 | export const NIGHTMODE = "NIGHTMODE";
12 | export const REQUEST_POSTS = "REQUEST_POSTS";
13 | export const RECEIVE_POSTS = "RECEIVE_POSTS";
14 | export const REMOVE_SUBREDDIT = "REMOVE_SUBREDDIT";
15 | export const SELECT_SUBREDDIT = "SELECT_SUBREDDIT";
16 | export const UPDATE_TOKENS = "UPDATE_TOKENS";
17 | export const UPVOTE = "UPVOTE";
18 |
--------------------------------------------------------------------------------
/src/components/OptTab.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import {
4 | StyledCanvas,
5 | StyledTab,
6 | StyledTabEntry,
7 | } from "../styles/components/tabStyles";
8 |
9 | class OptTab extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | }
13 | render() {
14 | return (
15 | this.props.toggleOpt(e)}
18 | >
19 |
20 | this.props.editComment()}>
21 | Edit
22 |
23 | this.props.del_comment()}
27 | >
28 | Delete
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | export default OptTab;
37 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./components/App";
4 | import { Provider } from "react-redux";
5 | import store from "./store";
6 | import { loggedIn, nightMode } from "./actions/index";
7 | import { refreshData } from "./actions/api-calls";
8 | import { addSubreddit } from "./actions";
9 |
10 | window.localStorage.removeItem("subreddits");
11 | let {
12 | data = "{}",
13 | nightMode: nightModeState = false,
14 | subreddits = "[]",
15 | } = window.localStorage;
16 | data = JSON.parse(data);
17 |
18 | nightModeState = nightModeState === "true";
19 | store.dispatch(nightMode(nightModeState));
20 |
21 | if (data.hasOwnProperty("access_token")) {
22 | refreshData(data.access_token, data.refresh_token).then((data) => {
23 | store.dispatch(loggedIn(data));
24 | });
25 | } else {
26 | JSON.parse(subreddits).map((sub) => {
27 | store.dispatch(addSubreddit(sub));
28 | });
29 | }
30 |
31 | ReactDOM.render(
32 |
33 |
34 | ,
35 | document.getElementById("root")
36 | );
37 |
--------------------------------------------------------------------------------
/src/styles/components/pageStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { FadeInLeft } from "./animations";
3 |
4 | export const StyledPageCanvas = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | position: fixed;
8 | width: 100%;
9 | height: 100%;
10 | color: ${({ theme }) => theme.color}
11 | overflow-x: hidden;
12 | background: ${({ theme }) => theme.bodybg}
13 | transition: all .3s ease-in-out;
14 | animation: ${FadeInLeft} .3s ease-in-out;
15 | `;
16 |
17 | export const StyledPageHeading = styled.div`
18 | display: flex;
19 | flex-direction: row;
20 | position: fixed;
21 | width: 100%;
22 | z-index: 11;
23 | background: ${({ theme }) => theme.bg};
24 | justify-content: flex-start;
25 | align-items: center;
26 | padding-left: 1rem;
27 | height: 4rem;
28 | border: none;
29 | border-bottom: 1px ${({ theme }) => theme.themeborder} solid;
30 | `;
31 |
32 | export const Styledbody = styled.div`
33 | display: flex;
34 | flex-direction: column;
35 | background: ${({ theme }) => theme.bg};
36 | color: ${({ theme }) => theme.color}
37 | width: 100%;
38 | height: 100%;
39 | overflow-y: scroll;
40 | height: 100%;
41 | `;
42 |
--------------------------------------------------------------------------------
/public/svg/send.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/styles/components/animations.js:
--------------------------------------------------------------------------------
1 | import { keyframes } from "styled-components";
2 |
3 | export const pop = keyframes`
4 | 0% { opacity: 0; -webkit-transform: scale(1); }
5 | 80% { opacity: 1; -webkit-transform: scale(1.1); }
6 | 101% { opacity: 1; -webkit-transform: scale(1); }
7 | `;
8 |
9 | export const popin = keyframes`
10 | 0% { opacity: 0; -webkit-transform: scale(0.5); }
11 | 80% { opacity: 0; -webkit-transform: scale(1.2); }
12 | 100% { opacity: 1; -webkit-transform: scale(1); }
13 | `;
14 |
15 | export const fadein = keyframes`
16 | 0% {opacity: 0;}
17 | 100% {opacity: 1;}
18 | `;
19 |
20 | export const FadeInUp = keyframes`
21 | 0% {
22 | opacity: 0;
23 | transform: translate3d(0, 100%, 0);
24 | }
25 |
26 | 100% {
27 | opacity: 1;
28 | transform: none;
29 | }
30 | `;
31 |
32 | export const FadeInRight = keyframes`
33 | 0% {
34 | opacity: 0;
35 | transform: translate3d(100%, 0, 0);
36 | }
37 |
38 | 100% {
39 | opacity: 1;
40 | transform: none;
41 | }
42 | `;
43 |
44 | export const FadeInLeft = keyframes`
45 | 0% {
46 | opacity: 0;
47 | transform: translate3d(-100%, 0, 0);
48 | }
49 |
50 | 100% {
51 | opacity: 1;
52 | transform: none;
53 | }
54 | `;
55 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ThemeProvider } from "styled-components";
3 | import AppRouter from "../router/AppRouter";
4 | import { connect } from "react-redux";
5 | import GlobalStyles from "../styles/base/base";
6 | import { colors } from "../styles/base/colors";
7 |
8 | const lighttheme = {
9 | name: "light",
10 | bodybg: colors.gray1,
11 | border: colors.gray2,
12 | themeborder: colors.gray2,
13 | bg: "white",
14 | color: "black",
15 | breakpoint: "42rem",
16 | colors: colors,
17 | };
18 |
19 | const darktheme = {
20 | name: "dark",
21 | bodybg: "#121212",
22 | border: "#363537",
23 | themeborder: colors.gray7,
24 | bg: "#363537",
25 | color: colors.gray4,
26 | breakpoint: "42rem",
27 | colors: colors,
28 | };
29 |
30 | class App extends React.Component {
31 | constructor(props) {
32 | super(props);
33 | }
34 | render() {
35 | return (
36 |
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | const mapStateToProps = (state) => {
47 | return {
48 | nightmode: state.settingsReducer.nightmode,
49 | };
50 | };
51 |
52 | export default connect(mapStateToProps)(App);
53 |
--------------------------------------------------------------------------------
/src/styles/components/windowStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { FadeInLeft } from "./animations";
3 |
4 | export const StyledMainWindow = styled.div`
5 | background: none;
6 | margin: 1.8rem 0;
7 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
8 | margin: 0;
9 | }
10 | transition: all 0.4s ease-in-out;
11 | `;
12 |
13 | export const StyledActiveWindow = styled.div`
14 | background: none;
15 | animation: ${FadeInLeft} 0.3s;
16 | transition: all 0.4s ease-in-out;
17 | `;
18 |
19 | export const StyledWindowContainer = styled.div`
20 | display: flex;
21 | flex-direction: row;
22 | justify-content: space-around;
23 | height: 100vh;
24 | animation: ${FadeInLeft} 0.3s;
25 | transition: all 0.4s ease-in-out;
26 | `;
27 |
28 | export const StyledContentBox = styled.div`
29 | display: flex;
30 | flex-direction: column;
31 | width: 50%;
32 | margin-top: 2rem;
33 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
34 | margin: 2rem 0;
35 | width: 100%;
36 | }
37 | &::-webkit-scrollbar {
38 | width: 2px;
39 | }
40 | transition: all 0.4s ease-in-out;
41 | `;
42 |
43 | export const StyledInfoBox = styled.div`
44 | align-self: top;
45 | background: none;
46 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
47 | display: none;
48 | }
49 | transition: all 0.4s ease-in-out;
50 | `;
51 |
--------------------------------------------------------------------------------
/src/components/Trophies.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getTrophies } from "../actions/api-calls";
3 | import {
4 | StyledCanvas,
5 | StyledTab,
6 | StyledTabHead,
7 | StyledTabValues,
8 | StyledTabBody,
9 | } from "../styles/components/tabStyles";
10 | import { StyledBtnImg } from "../styles/components/headerStyles";
11 |
12 | class Trophies extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | trophies: {},
17 | };
18 | }
19 | handleCloseTab = (e) => {
20 | if (e.target.getAttribute("type") === "canvas") {
21 | this.props.closeTab("trophies");
22 | }
23 | };
24 | async componentDidMount() {
25 | this.setState({ trophies: await getTrophies(this.props.token) });
26 | }
27 | render() {
28 | return (
29 | this.handleCloseTab(e)}>
30 |
31 | Trophies
32 |
33 | {Object.entries(this.state.trophies).map(
34 | ([key, value], index) => {
35 | return (
36 |
37 |
38 | {value.name}
39 |
40 | );
41 | }
42 | )}
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default Trophies;
51 |
--------------------------------------------------------------------------------
/src/components/Settings.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { nightMode } from "../actions";
4 | import {
5 | StyledSwitch,
6 | StyledLabelText,
7 | } from "../styles/components/settingStyles";
8 | import { StyledCanvas, StyledTab } from "../styles/components/tabStyles";
9 |
10 | class Settings extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | checked: true,
15 | };
16 | }
17 | handleCloseSettings = (e) => {
18 | if (e.target.getAttribute("type") === "canvas") {
19 | this.props.closeTab("settings");
20 | }
21 | };
22 | render() {
23 | return (
24 | this.handleCloseSettings(e)}
27 | >
28 |
29 |
30 |
31 | this.props.dispatch(nightMode())}
35 | />
36 |
37 |
38 | Night Mode
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | const mapStateToProps = (state) => {
47 | return {
48 | nightmode: state.settingsReducer.nightmode,
49 | };
50 | };
51 |
52 | export default connect(mapStateToProps)(Settings);
53 |
--------------------------------------------------------------------------------
/src/styles/components/settingStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { fadein, popin } from "./animations";
3 |
4 | export const StyledSwitch = styled.label`
5 | position: relative;
6 | display: inline-block;
7 | float: left;
8 | width: 38px;
9 | height: 18px;
10 | & > input {
11 | opacity: 0;
12 | width: 0;
13 | height: 0;
14 | }
15 | & > .slider {
16 | position: absolute;
17 | cursor: pointer;
18 | top: 0;
19 | left: 0;
20 | right: 0;
21 | bottom: 0;
22 | background-color: #ccc;
23 | -webkit-transition: 0.4s;
24 | transition: 0.4s;
25 | }
26 | & > .slider,
27 | & > .round {
28 | border-radius: 34px;
29 | }
30 | & > .slider:before {
31 | position: absolute;
32 | content: "";
33 | height: 16px;
34 | width: 16px;
35 | top: 1px;
36 | left: 2px;
37 | background-color: white;
38 | -webkit-transition: 0.4s;
39 | transition: 0.4s;
40 | }
41 | input:checked + .slider {
42 | background-color: #2196f3;
43 | }
44 |
45 | input:focus + .slider {
46 | box-shadow: 0 0 1px #2196f3;
47 | }
48 |
49 | input:checked + .slider:before {
50 | -webkit-transform: translateX(18px);
51 | -ms-transform: translateX(18px);
52 | transform: translateX(18px);
53 | }
54 |
55 | .slider.round:before {
56 | border-radius: 50%;
57 | }
58 | `;
59 |
60 | export const StyledLabelText = styled.div`
61 | float: left;
62 | margin: 0 1rem;
63 | font-size: 0.7rem;
64 | font-size: 1rem;
65 | `;
66 |
--------------------------------------------------------------------------------
/src/components/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Redditor from "./Redditor";
3 | import Header from "./Header";
4 | import ContentBox from "./ContentBox";
5 | import Menubar from "./Menubar";
6 | import User from "./User";
7 | import { connect } from "react-redux";
8 | import { getHomePage } from "../actions/index";
9 | import {
10 | StyledWindowContainer,
11 | StyledContentBox,
12 | StyledInfoBox,
13 | } from "../styles/components/windowStyles";
14 |
15 | class Dashboard extends Component {
16 | constructor(props) {
17 | super(props);
18 | this.textInputRef = React.createRef();
19 | this.focusTextInput = this.focusTextInput.bind(this);
20 | }
21 |
22 | focusTextInput = () => {
23 | this.textInputRef.current.focus();
24 | };
25 |
26 | componentDidMount() {
27 | this.props.dispatch(getHomePage());
28 | }
29 | render() {
30 | return (
31 |
32 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 | }
51 | }
52 |
53 | export default connect()(Dashboard);
54 |
--------------------------------------------------------------------------------
/src/components/Comments.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | StyledComment,
4 | StyledCommentUps,
5 | StyledLinkBtn,
6 | } from "../styles/components/commentStyles";
7 |
8 | const Comments = (props) => (
9 |
10 | {!props.count ? (
11 |
12 | view comments
13 |
14 | ) : (
15 |
16 | {props.comments.slice(0, props.count).map((data, index) => {
17 | if (!data.hasOwnProperty("comment")) return;
18 | return (
19 |
20 |
21 | {data.author}
22 | {data.comment}
23 |
24 |
25 | {data.author === props.username && (
26 |
30 | props.toggleOpt(e, data.name, index)
31 | }
32 | >
33 | ...
34 |
35 | )}
36 | +{data.ups}
37 |
38 |
39 | );
40 | })}
41 | {props.count < props.comments.length ? (
42 |
43 | view more
44 |
45 | ) : (
46 |
47 | hide comments
48 |
49 | )}
50 |
51 | )}
52 |
53 | );
54 |
55 | export default Comments;
56 |
--------------------------------------------------------------------------------
/src/router/AppRouter.js:
--------------------------------------------------------------------------------
1 | import { Router, Switch, Route } from "react-router-dom";
2 | import React from "react";
3 | import { createBrowserHistory } from "history";
4 | import Dashboard from "../components/Dashboard";
5 | import Explore from "../components/Explore";
6 | import FetchToken from "../components/FetchToken";
7 | import Inbox from "../components/Inbox";
8 | import Notifications from "../components/Notifications";
9 | import SubredditProfile from "../components/SubredditProfile";
10 | import Profile from "../components/Profile";
11 | import About from "../components/About";
12 | import NotFound from "../components/NotFound";
13 | import PrivateRoute from "./PrivateRoute";
14 |
15 | export const history = createBrowserHistory();
16 |
17 | const AppRouter = () => (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
34 |
35 |
36 |
37 | );
38 |
39 | export default AppRouter;
40 |
--------------------------------------------------------------------------------
/src/components/Menubar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import { deselect_subreddit } from "../actions";
5 | import { StyledBtnImg } from "../styles/components/headerStyles";
6 | import { StyledMenubar } from "../styles/components/menubarStyles";
7 |
8 | const Menubar = (props) => (
9 |
10 |
11 | props.dispatch(deselect_subreddit())}
13 | type="home"
14 | src={
15 | props.nightmode
16 | ? "/images/home-light.png"
17 | : "/images/home.png"
18 | }
19 | alt="home"
20 | />
21 |
22 |
23 |
32 |
33 |
34 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 |
50 | const mapStateToProps = (state) => {
51 | return {
52 | icon_img: state.authenticationReducer.user.icon_img,
53 | nightmode: state.settingsReducer.nightmode,
54 | };
55 | };
56 |
57 | export default connect(mapStateToProps)(Menubar);
58 |
--------------------------------------------------------------------------------
/src/styles/components/commentStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledComment = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | justify-content: space-between;
7 | & > div > .opt {
8 | display: none;
9 | cursor: pointer;
10 | }
11 | &:hover {
12 | & > div > .opt {
13 | display: block;
14 | }
15 | }
16 | transition: all 0.2s ease;
17 | `;
18 |
19 | export const StyledCommentUps = styled.b`
20 | float: right;
21 | `;
22 |
23 | export const StyledLinkBtn = styled.p`
24 | color: gray;
25 | cursor: pointer;
26 | `;
27 |
28 | export const StyledCommentBox = styled.div`
29 | display: flex;
30 | flex-direction: row;
31 | align-self: flex-start;
32 | margin-left: -1rem;
33 | width: 100%;
34 | scroll-behavior: smooth;
35 | transition: all 0.2s ease;
36 | `;
37 |
38 | export const StyledCommentInput = styled.input`
39 | outline: none;
40 | width: 100%;
41 | height: 2rem;
42 | padding: 0.1rem 0.8rem;
43 | background: ${({ theme }) =>
44 | theme.name === "light" ? theme.colors.gray2 : theme.colors.gray4};
45 | border: none;
46 | &:placeholder {
47 | color: ${({ theme }) =>
48 | theme.name === "light" ? theme.colors.gray1 : theme.colors.gray7};
49 | }
50 | `;
51 |
52 | export const StyledPostButton = styled.button`
53 | border: none;
54 | outline: none;
55 | background: transparent;
56 | cursor: pointer;
57 | margin-right: -1rem;
58 | &:disabled {
59 | color: ${({ theme }) => theme.colors.gray3};
60 | }
61 | &:enabled {
62 | color: ${({ theme }) => theme.colors.blue};
63 | }
64 | `;
65 |
--------------------------------------------------------------------------------
/src/reducers/authenticationReducer.js:
--------------------------------------------------------------------------------
1 | import { LOGGED_IN, LOGOUT, UPDATE_TOKENS } from "../actions/action-types";
2 |
3 | const initialState = {
4 | authenticated: false,
5 | access_token: undefined,
6 | expires_in: 3600,
7 | refresh_token: undefined,
8 | scope:
9 | "account creddits edit privatemessages mysubreddits history identity submit subscribe vote",
10 | user: {
11 | icon_img: "/images/defaulticon.png",
12 | id: "user101",
13 | name: "reddituser",
14 | num_friends: undefined,
15 | pref_nightmode: false,
16 | total_karma: undefined,
17 | },
18 | };
19 |
20 | const authenticationReducer = (state = initialState, action) => {
21 | switch (action.type) {
22 | case LOGGED_IN: {
23 | const {
24 | access_token,
25 | expires_in,
26 | refresh_token,
27 | scope,
28 | } = action.payload.data;
29 | const {
30 | icon_img,
31 | id = "randomid",
32 | name = "reddituser",
33 | num_friends,
34 | pref_nightmode = false,
35 | total_karma,
36 | } = action.payload.userInfo;
37 | return {
38 | authenticated: true,
39 | access_token,
40 | expires_in,
41 | refresh_token,
42 | scope,
43 | user: {
44 | icon_img,
45 | id,
46 | name,
47 | num_friends,
48 | pref_nightmode,
49 | total_karma,
50 | },
51 | };
52 | }
53 | case LOGOUT: {
54 | return {
55 | ...initialState,
56 | };
57 | }
58 | case UPDATE_TOKENS: {
59 | return {
60 | ...state,
61 | access_token: action.payload.access_token,
62 | };
63 | }
64 | default:
65 | return state;
66 | }
67 | };
68 |
69 | export default authenticationReducer;
70 |
--------------------------------------------------------------------------------
/src/styles/components/postStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledPostContainer = styled.div`
4 | display: flex;
5 | background: ${({ theme }) => theme.bg};
6 | border: ${({ theme }) => theme.border} 1px solid;
7 | border-radius: 3px;
8 | height: 100%;
9 | flex-direction: column;
10 | margin-bottom: 4rem;
11 | overflow-wrap: break-word;
12 | text-align: justify;
13 | width: 100.4%;
14 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
15 | margin: 0;
16 | padding-bottom: 1.8rem;
17 | border: none;
18 | }
19 | `;
20 |
21 | export const StyledPostTop = styled.div`
22 | border: none;
23 | border-bottom: 1px ${({ theme }) => theme.themeborder} solid;
24 | font-weight: 600;
25 | height: 4rem;
26 | padding-top: 0.3rem;
27 | & > .pt__username {
28 | color: ${({ theme }) => theme.color};
29 | display: flex;
30 | text-decoration: none;
31 | padding-top: 1rem;
32 | }
33 | `;
34 | export const StyledPostMid = styled.div`
35 | min-height: 14rem;
36 | & > .text-post {
37 | color: ${({ theme }) =>
38 | (theme.name === "dark" && "white") || "inherit"};
39 | padding: 0 1.5rem;
40 | min-height: 4rem;
41 | max-height: 22rem;
42 | overflow-y: scroll;
43 | }
44 | & > .text-post > a {
45 | color: ${({ theme }) =>
46 | (theme.name === "dark" && "white") || "inherit"};
47 | }
48 | & > .text-post::-webkit-scrollbar {
49 | width: 8px;
50 | }
51 | transition: all 0.1s ease-in-out;
52 | `;
53 |
54 | export const StyledPostBottom = styled.div`
55 | border: none;
56 | padding: 1rem;
57 | `;
58 |
59 | export const StyledInlineButtons = styled.div`
60 | display: flex;
61 | flex-direction: row;
62 | `;
63 |
--------------------------------------------------------------------------------
/src/components/RenderSubs.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { addSubreddit, select_subreddit, fetchPosts } from "../actions";
4 | import {
5 | StyledSubContainer,
6 | StyledSub,
7 | StyledSubIcon,
8 | } from "../styles/components/redditorStyles";
9 | import { StyledAddBtn } from "../styles/components/searchbarStyles";
10 |
11 | class RenderSubs extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.switchSub = this.switchSub.bind(this);
15 | }
16 |
17 | switchSub = (e) => {
18 | this.props.dispatch(select_subreddit(e.target.name));
19 | this.props.dispatch(fetchPosts(e.target.name));
20 | };
21 |
22 | render() {
23 | const that = this;
24 | return (
25 |
26 |
27 | +
28 |
29 | {Object.entries(this.props.subs)
30 | .reverse()
31 | .map(([subName, sub], index) => {
32 | return (
33 |
34 |
39 |
46 |
47 | r/{subName.substring(0, 8)}
48 | {subName.length > 8 && .. }
49 |
50 |
51 |
52 | );
53 | }, that)}
54 |
55 | );
56 | }
57 | }
58 |
59 | export default connect()(RenderSubs);
60 |
--------------------------------------------------------------------------------
/src/components/FollowingTab.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 | import { removeSubreddit } from "../actions";
4 | import {
5 | StyledFollowingTab,
6 | StyledFTab,
7 | StyledFTabHeader,
8 | StyledFTabText,
9 | StyledFTabCloseBtn,
10 | StyledFollowings,
11 | StyledFollowing,
12 | StyledUnfollowBtn,
13 | } from "../styles/components/followingtabStyles";
14 |
15 | const Followings = ({ followings, closeTab, dispatch }) => {
16 | return (
17 |
18 |
19 |
20 | Following
21 | closeTab("followingTab")}
23 | >
24 | +
25 |
26 |
27 |
28 | {Object.entries(followings).map(
29 | ([subname, details], index) => {
30 | return (
31 |
32 |
36 |
37 | {subname.length > 12 ? (
38 | {subname.substring(0, 12)}...
39 | ) : (
40 | {subname}
41 | )}
42 |
43 |
44 | {
45 |
47 | dispatch(
48 | removeSubreddit(subname)
49 | )
50 | }
51 | >
52 | Unfollow
53 |
54 | }
55 |
56 |
57 | );
58 | }
59 | )}
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | export default Followings;
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redditfornormies",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "axios": "^0.21.1",
10 | "cors": "^2.8.5",
11 | "cross-env": "^7.0.2",
12 | "dotenv": "^8.2.0",
13 | "express": "^4.17.1",
14 | "history": "^4.10.1",
15 | "install": "^0.13.0",
16 | "js-cookie": "^2.2.1",
17 | "lodash": "^4.17.19",
18 | "lodash.debounce": "^4.0.8",
19 | "moment": "^2.27.0",
20 | "node-sass": "^4.14.1",
21 | "nodemon": "^2.0.4",
22 | "npm": "^7.4.3",
23 | "query-string": "^6.13.1",
24 | "react": "^16.13.1",
25 | "react-dom": "^16.13.1",
26 | "react-redux": "^7.2.0",
27 | "react-router-dom": "^5.2.0",
28 | "react-scripts": "^4.0.1",
29 | "redux": "^4.0.5",
30 | "redux-cookies-middleware": "^0.2.1",
31 | "redux-thunk": "^2.3.0",
32 | "styled-components": "^5.1.1",
33 | "superagent": "^5.3.1",
34 | "websocket-extensions": "^0.1.4"
35 | },
36 | "scripts": {
37 | "start": "node server/server.js",
38 | "build": "react-scripts build",
39 | "test": "react-scripts test",
40 | "eject": "react-scripts eject",
41 | "devserver": "react-scripts start",
42 | "heroku-postbuild": "npm run build"
43 | },
44 | "engines": {
45 | "node": "12.13.1"
46 | },
47 | "eslintConfig": {
48 | "extends": "react-app"
49 | },
50 | "browserslist": {
51 | "production": [
52 | ">0.2%",
53 | "not dead",
54 | "not op_mini all"
55 | ],
56 | "development": [
57 | "last 1 chrome version",
58 | "last 1 firefox version",
59 | "last 1 safari version"
60 | ]
61 | },
62 | "devDependencies": {
63 | "prettier": "^2.0.5"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/Redditor.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import RenderSubs from "./RenderSubs";
4 | import {
5 | StyledRedditor,
6 | StyledScrollBtn,
7 | } from "../styles/components/redditorStyles";
8 |
9 | class Redditor extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | scrollpos: 0,
14 | scrollWidth: undefined,
15 | };
16 | this.redditor = React.createRef();
17 | }
18 |
19 | scrollLeft = () => {
20 | this.redditor.current.scrollLeft -= 500;
21 | this.setState({
22 | scrollpos: this.redditor.current.scrollLeft - 500,
23 | scrollWidth: this.redditor.current.scrollWidth,
24 | });
25 | };
26 |
27 | scrollRight = () => {
28 | this.redditor.current.scrollLeft += 500;
29 | this.setState({
30 | scrollpos: this.redditor.current.scrollLeft + 500,
31 | scrollWidth: this.redditor.current.scrollWidth,
32 | });
33 | };
34 |
35 | componentDidMount() {
36 | this.setState({ scrollWidth: this.redditor.current.scrollWidth });
37 | }
38 |
39 | render() {
40 | return (
41 |
42 | {this.state.scrollpos > 50 && (
43 |
44 | >
45 |
46 | )}
47 |
48 |
53 |
54 | {this.state.scrollpos < this.state.scrollWidth - 450 && (
55 |
59 | >
60 |
61 | )}
62 |
63 | );
64 | }
65 | }
66 |
67 | const mapStateToProps = (state, props) => {
68 | return {
69 | subreddits: state.postReducer.data,
70 | activeSub: state.subredditReducer.activeSubreddit,
71 | };
72 | };
73 |
74 | export default connect(mapStateToProps)(Redditor);
75 |
--------------------------------------------------------------------------------
/src/reducers/subredditReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_SUBREDDIT,
3 | ADD_SUBREDDITS,
4 | SELECT_SUBREDDIT,
5 | DESELECT_SUBREDDIT,
6 | LOGGED_IN,
7 | LOGOUT,
8 | REMOVE_SUBREDDIT,
9 | } from "../actions/action-types";
10 |
11 | const initialState = {
12 | activeSubreddit: undefined,
13 | subreddits: [],
14 | };
15 |
16 | const subredditReducer = (state = initialState, action) => {
17 | switch (action.type) {
18 | case ADD_SUBREDDIT:
19 | if (state.subreddits.includes(action.payload.subreddit))
20 | return state;
21 | let subreddits =
22 | state.subreddits !== undefined
23 | ? [...state.subreddits, action.payload.subreddit]
24 | : [action.payload.subreddit];
25 | if (!action.payload.authenticated) {
26 | window.localStorage.setItem(
27 | "subreddits",
28 | JSON.stringify(subreddits)
29 | );
30 | }
31 | return {
32 | ...state,
33 | subreddits,
34 | };
35 | case ADD_SUBREDDITS:
36 | return {
37 | ...state,
38 | subreddits: [...state.subreddits, ...action.payload.subs],
39 | };
40 | case SELECT_SUBREDDIT:
41 | return {
42 | ...state,
43 | activeSubreddit: action.payload.subreddit,
44 | };
45 | case DESELECT_SUBREDDIT:
46 | return {
47 | ...state,
48 | activeSubreddit: undefined,
49 | };
50 | case REMOVE_SUBREDDIT: {
51 | const subreddits = state.subreddits.filter(
52 | (sub) => sub !== action.payload.subreddit
53 | );
54 | window.localStorage.setItem(
55 | "subreddits",
56 | JSON.stringify(subreddits)
57 | );
58 | const activeSubreddit =
59 | state.activeSubreddit === action.payload.subreddit
60 | ? undefined
61 | : state.activeSubreddit;
62 | return {
63 | activeSubreddit,
64 | subreddits,
65 | };
66 | }
67 | case LOGGED_IN:
68 | return {
69 | activeSubreddit: undefined,
70 | subreddits: [],
71 | };
72 | case LOGOUT: {
73 | window.location.reload(false);
74 | return {
75 | ...state,
76 | subreddits,
77 | };
78 | }
79 | default:
80 | return state;
81 | }
82 | };
83 |
84 | export default subredditReducer;
85 |
--------------------------------------------------------------------------------
/src/styles/components/headerStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledHeader = styled.div`
4 | background: ${(props) => props.theme.bg};
5 | border: 2px ${({ theme }) => theme.border} solid;
6 | display: flex;
7 | flex-direction: row;
8 | height: 3.5rem;
9 | align-items: center;
10 | justify-content: space-between;
11 | position: fixed;
12 | top: 0;
13 | width: 100%;
14 | z-index: 10;
15 | @media (max-width: ${(props) => props.theme.breakpoint}) {
16 | height: 2.5rem;
17 | border: none;
18 | }
19 | & > .header-text {
20 | font-family: "Grand Hotel", cursive;
21 | font-size: 2.2rem;
22 | font-weight: 550;
23 | color: ${({ theme }) => theme.color};
24 | text-decoration: none;
25 | margin-left: 12%;
26 | @media (max-width: ${(props) => props.theme.breakpoint}) {
27 | margin: 0;
28 | margin-left: 38%;
29 | font-size: 1.6rem;
30 | }
31 | }
32 | `;
33 |
34 | export const StyledHeaderBtns = styled.div`
35 | display: flex;
36 | flex-direction: row;
37 | justify-content: space-around;
38 | height: 2rem;
39 | padding-right: 14%;
40 | @media (max-width: ${(props) => props.theme.breakpoint}) {
41 | display: none;
42 | }
43 | `;
44 |
45 | export const StyledBtnImg = styled.img`
46 | border: none;
47 | border-radius: 50%;
48 | cursor: pointer;
49 | height: 1.6rem;
50 | width: 1.6rem;
51 | margin: 0 .6rem;
52 | transition: all .1s ease;
53 | ${(props) => {
54 | switch (props.type) {
55 | case "inbox":
56 | return "transform: scale(1.2);margin: 0 .4rem;";
57 | case "compas":
58 | return "height: 1.5rem;";
59 | case "like":
60 | return "height: 1.55rem;";
61 | case "upvote":
62 | return "margin: 0;padding: 0;height: 1.4rem;";
63 | case "downvote":
64 | return "margin: 0;padding: 0;height: 1.4rem;transform: rotate(180deg);";
65 | case "headerInbox":
66 | return "display: none;position: fixed;transform: rotate(15deg);width: 2rem;height: 2rem;z-index: 11;right:1%;";
67 | case "arrow":
68 | return "display: none;position: absolute;left: 0;top: 2%;transform: scale(1);";
69 | case "comment":
70 | return "width: 1rem;height: 1rem;";
71 | }
72 | }}
73 | @media(max-width: ${({ theme }) => theme.breakpoint}){
74 | display: block;
75 | }
76 | `;
77 |
--------------------------------------------------------------------------------
/src/styles/components/tabStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { fadein, popin } from "./animations";
3 |
4 | export const StyledCanvas = styled.div`
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | position: fixed;
9 | background: rgba(0, 0, 0, 0.4);
10 | width: 100vw;
11 | height: 100vh;
12 | top: 0;
13 | left: 0;
14 | z-index: 11;
15 | transition: all 0.1s ease-in-out;
16 | animation: ${fadein} 0.1s;
17 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
18 | height: 100%;
19 | width: 100%;
20 | }
21 | `;
22 |
23 | export const StyledTab = styled.div`
24 | background: ${({ theme }) => theme.bg};
25 | display: flex;
26 | flex-direction: column;
27 | align-items: flex-start;
28 | min-width: 16rem;
29 | max-width: 16rem;
30 | min-height: ${(props) => (props.height && props.height) || "22rem"};
31 | max-height: 22rem;
32 | padding: 1rem;
33 | border: none;
34 | border-radius: 6px;
35 | z-index: 12;
36 | animation: ${popin} 0.3s;
37 | ${(props) => {
38 | switch (props.type) {
39 | case "opt":
40 | return "justify-content: center;align-items: center;";
41 | }
42 | }}
43 | `;
44 |
45 | export const StyledTabHead = styled.div`
46 | width: 100%;
47 | display: flex;
48 | justify-content: center;
49 | align-self: center;
50 | border-bottom: 1px solid ${({ theme }) => theme.colors.gray7};
51 | font-weight: 600;
52 | padding: 0.1rem 0;
53 | `;
54 |
55 | export const StyledTabValues = styled.div`
56 | display: flex;
57 | flex-direction: row;
58 | align-items: center;
59 | justify-content: flex-start;
60 | border-bottom: 1px solid ${({ theme }) => theme.colors.gray5};
61 | min-width: 100%;
62 | max-height: 3rem;
63 | flex-basis: 100%;
64 | text-align: center;
65 | text-overflow: elipsis;
66 | overflow-wrap: wrap;
67 | animation: ${fadein} 0.1s;
68 | `;
69 |
70 | export const StyledTabBody = styled.div`
71 | display: flex;
72 | flex-direction: column;
73 | width: 100%;
74 | min-height: 100%;
75 | overflow-y: scroll;
76 | &::-webkit-scrollbar {
77 | width: 0px;
78 | }
79 | `;
80 |
81 | export const StyledTabEntry = styled.p`
82 | display: flex;
83 | flex-direction: row;
84 | align-items: center;
85 | justify-content: center;
86 | cursor: pointer;
87 | color: ${(props) => (props.color && props.color) || props.theme.color};
88 | `;
89 |
--------------------------------------------------------------------------------
/src/styles/components/exploreStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { popin, FadeInRight } from "./animations";
3 |
4 | export const StyledExplorer = styled.div`
5 | display: flex;
6 | flex-flow: row wrap;
7 | float: left;
8 | justify-content: flex-start;
9 | margin: 5rem 8rem;
10 | width: 80%;
11 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
12 | width: 100%;
13 | margin: 0;
14 | }
15 | transition: all 0.1s ease-in-out;
16 | animation: ${FadeInRight} 0.1s ease;
17 | `;
18 |
19 | export const StyledExplorePost = styled.div`
20 | background-color: white;
21 | border: 1px solid ${({ theme }) => theme.border};
22 | display: flex;
23 | flex-direction: column;
24 | flex-grow: 4;
25 | flex-shrink: 1;
26 | height: 18rem;
27 | min-height: 15rem;
28 | margin: 0.5rem;
29 | max-width: 15.8rem;
30 | min-width: 15.8rem;
31 | overflow: hidden;
32 | position: relative;
33 | cursor: pointer;
34 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
35 | width: 8.6rem;
36 | min-width: 8.6rem;
37 | min-height: 8rem;
38 | height: 12rem;
39 | margin: 0.1rem;
40 | }
41 | animation: ${popin} 1s ease-in-out;
42 | `;
43 |
44 | export const StyledExplorePostImg = styled.img`
45 | background-size: cover;
46 | background: transparent
47 | url("https://static.wixstatic.com/media/d63215_a6ccc92841c74c6d835a02dac366b158~mv2.gif")
48 | center no-repeat;
49 | height: 100%;
50 | width: 100%;
51 | &:hover {
52 | filter: brightness(70%);
53 | transition: filter 0.1s ease-in-out;
54 | }
55 | `;
56 |
57 | export const StyledExploreTextPost = styled.div`
58 | height: 100%;
59 | padding: 1rem;
60 | text-align: left;
61 | background: ${({ theme }) => theme.bg};
62 | color: ${({ theme }) => theme.color};
63 | word-wrap: break-word;
64 | &:hover {
65 | background: ${({ theme }) =>
66 | (theme.name === "light" && "rgba(0,0,0,.2)") ||
67 | "rgba(54, 53, 55,.9)"};
68 | height: 100%;
69 | transition: background 0.1s ease-in-out;
70 | }
71 | `;
72 |
73 | export const StyledHoverDisplay = styled.h2`
74 | align-self: center;
75 | color: white;
76 | text-shadow: 1px ${({ theme }) => theme.colors.gray2};
77 | top: 40%;
78 | position: absolute;
79 | z-index: 5;
80 | `;
81 |
82 | export const StyledUpvoteIcon = styled.img`
83 | height: 1.5rem;
84 | width: 1.2rem;
85 | z-index: 7;
86 | `;
87 |
--------------------------------------------------------------------------------
/src/styles/components/notificationbarStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledNotificationBarContainer = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | position: fixed;
7 | background: ${({ theme }) =>
8 | (theme.name === "light" && "white") || theme.bg};
9 | top: 4rem;
10 | border-radius: 6px;
11 | right: 12%;
12 | width: 40%;
13 | min-height: 6rem;
14 | max-height: 20rem;
15 | overflow-x: hidden;
16 | overflow-y: scroll;
17 | z-index: 3;
18 | ${(props) => {
19 | if (props.theme.name === "light") {
20 | return "box-shadow: 4px 4px 1rem gray;border: 1px ${({theme})=>theme.color} solid;";
21 | } else if (props.theme.name === "dark") {
22 | return "border: 1px ${({theme})=>theme.colors.gray7} solid;";
23 | }
24 | }}
25 | `;
26 |
27 | export const StyledNotification = styled.div`
28 | width: 100%;
29 | min-height: 5.4rem;
30 | background: ${({ theme }) => theme.bg};
31 | border-bottom: 1px solid ${({ theme }) => theme.themeborder};
32 | padding: 0 1rem;
33 | overflow: hidden;
34 | cursor: pointer;
35 | display: flex;
36 | flex-direction: column;
37 | `;
38 |
39 | export const StyledNFooter = styled.div`
40 | display: flex;
41 | flex-direction: row;
42 | font-size: 12px;
43 | align-items: flex-end;
44 | justify-content: space-between;
45 | & > div {
46 | display: flex;
47 | flex-direction: row;
48 | margin-bottom: 0.8rem;
49 | }
50 | `;
51 | export const StyledNotificationBar = styled.div`
52 | &::before {
53 | position: fixed;
54 | right: 17.8%;
55 | top: 2.85rem;
56 | width: 0;
57 | height: 0;
58 | margin-top: -10px;
59 | border-top: 15px solid transparent;
60 | border-left: 15px solid transparent;
61 | border-right: 15px solid transparent;
62 | border-bottom: 15px solid
63 | ${({ theme }) => (theme.name === "light" && "white") || theme.bg};
64 | z-index: 99;
65 | content: "";
66 | }
67 | &::after {
68 | position: fixed;
69 | top: 2.85rem;
70 | right: 17.8%;
71 | width: 0;
72 | height: 0;
73 | filter: blur(2px);
74 | margin-top: -12px;
75 | border-top: 15px solid transparent;
76 | border-left: 15px solid transparent;
77 | border-right: 15px solid transparent;
78 | border-bottom: 13px solid
79 | ${({ theme }) =>
80 | (theme.name === "light" && theme.colors.gray3) || theme.bodybg};
81 | z-index: 98;
82 | content: "";
83 | }
84 | `;
85 |
--------------------------------------------------------------------------------
/src/components/FetchToken.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "./Header";
3 | import { Redirect } from "react-router-dom";
4 | import { connect } from "react-redux";
5 | import { loggedIn } from "../actions";
6 | import queryString from "query-string";
7 | import {
8 | StyledPageCover,
9 | StyledImg,
10 | } from "../styles/components/notFoundPageStyles";
11 |
12 | class FetchToken extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | loading: true,
17 | error: undefined,
18 | };
19 | this.setAccessToken = this.setAccessToken.bind(this);
20 | }
21 | setAccessToken() {
22 | const params = queryString.parse(this.props.location.search);
23 | const code = params.code;
24 | const clientID = process.env.REACT_APP_CLIENTID;
25 | const clientSecret = process.env.REACT_APP_CLIENTSECRET;
26 | const redirectUri = process.env.REACT_APP_REDIRECTURI;
27 | if (!code) return null;
28 | const encode = btoa(
29 | `${process.env.REACT_APP_CLIENTID}:${process.env.REACT_APP_CLIENTSECRET}`
30 | );
31 | const redditTokens = fetch(
32 | "https://www.reddit.com/api/v1/access_token",
33 | {
34 | method: "POST",
35 | body: `grant_type=authorization_code&code=${code}&redirect_uri=${redirectUri}&duration=permanent`,
36 | headers: {
37 | "Content-Type": "application/x-www-form-urlencoded",
38 | Authorization: `basic ${encode}`,
39 | },
40 | }
41 | )
42 | .then((res) => res.json())
43 | .then((data) => {
44 | if (data.hasOwnProperty("error"))
45 | this.setState({ error: data.error, loading: false });
46 | if (data.hasOwnProperty("access_token")) {
47 | this.setState({ loading: false });
48 | this.props.dispatch(loggedIn(data));
49 | this.props.history.push("/");
50 | }
51 | })
52 | .catch((error) => {
53 | this.setState({ error, loading: false });
54 | });
55 | }
56 | componentDidMount() {
57 | this.setAccessToken();
58 | }
59 | render() {
60 | return (
61 |
62 | {this.state.loading && (
63 |
64 |
65 | Loading...
66 |
67 | )}
68 | {!this.state.loading && this.state.error && (
69 |
70 |
71 |
72 | Somthing went wrong
73 |
74 |
75 | )}
76 |
77 | );
78 | }
79 | }
80 |
81 | export default connect()(FetchToken);
82 |
--------------------------------------------------------------------------------
/src/styles/components/inboxStyles.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from "styled-components";
2 | import { FadeInRight } from "./animations";
3 |
4 | export const StyledDM = styled.div`
5 | display: flex;
6 | flex-direction: row;
7 | position: fixed;
8 | top: 12%;
9 | justify-content: center;
10 | margin: 0 15%;
11 | background: ${({ theme }) => theme.bg};
12 | overflow: hidden;
13 | width: 70%;
14 | height: 85%;
15 | border: 1px solid ${({ theme }) => theme.themeborder};
16 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
17 | positon: default;
18 | border: none;
19 | width: 100%;
20 | height: 100%;
21 | margin: 0;
22 | padding: 0;
23 | top: 0%;
24 | z-index: 12;
25 | }
26 | transition: all 0.1s ease;
27 | animation: ${FadeInRight} 0.3s ease-in-out;
28 | `;
29 |
30 | export const StyledDMUsers = styled.div`
31 | width: 50%;
32 | float: left;
33 | border: none;
34 | border-right: 1px solid ${({ theme }) => theme.themeborder};
35 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
36 | width: 100%;
37 | }
38 | `;
39 |
40 | export const StyledDMHead = styled.div`
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | border-bottom: 1px solid ${({ theme }) => theme.themeborder};
45 | & > p {
46 | color: ${({ theme }) => theme.color};
47 | font-weight: 700;
48 | font-size: 1rem;
49 | }
50 | `;
51 |
52 | export const StyledDMUserThread = styled.div`
53 | height: 90%;
54 | padding: 0.4rem;
55 | overflow-y: scroll;
56 | `;
57 |
58 | export const StyledMessages = styled.div`
59 | width: 100%;
60 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
61 | display: none;
62 | }
63 | `;
64 |
65 | export const StyledChatHome = styled.div`
66 | display: flex;
67 | flex-direction: column;
68 | justify-content: center;
69 | align-items: center;
70 | height: 100%;
71 | & > img {
72 | width: 6rem;
73 | height: 6rem;
74 | border: 2px solid black;
75 | border-radius: 50%;
76 | padding: 0.8rem 1rem 0.4rem 0.4rem;
77 | top: 39%;
78 | margin-bottom: 1rem;
79 | }
80 | & > h2 {
81 | font-weight: 200;
82 | margin: 0.6rem;
83 | }
84 | > p {
85 | font-size: 0.9rem;
86 | color: ${({ theme }) => theme.colors.gray4};
87 | margin: 0;
88 | }
89 | `;
90 |
91 | export const StyledUser = styled.div`
92 | display: flex;
93 | flex-direction: row;
94 | padding: 0.5rem;
95 | cursor: pointer;
96 | & > img {
97 | width: 3rem;
98 | height: 3rem;
99 | border: none;
100 | border-radius: 50%;
101 | }
102 | `;
103 |
--------------------------------------------------------------------------------
/src/components/ContentBox.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import LoadPost from "./LoadPost";
4 | import debounce from "lodash.debounce";
5 | import {
6 | StyledMainWindow,
7 | StyledActiveWindow,
8 | } from "../styles/components/windowStyles.js";
9 | import { StyledLoader } from "../styles/components/profileStyles";
10 | import { StyledDiv } from "../styles/components/generalStyles";
11 | import { getHomePage, fetchPosts } from "../actions/index";
12 |
13 | const ContentBox = (props) => {
14 | const { activeSub, data } = props;
15 | const ishome = !!!props.activeSub;
16 | if (!ishome) {
17 | var items = data[activeSub].items;
18 | }
19 | window.onscroll = debounce(() => {
20 | if (
21 | window.innerHeight + document.documentElement.scrollTop ===
22 | document.documentElement.scrollHeight
23 | ) {
24 | if (ishome) props.dispatch(getHomePage());
25 | else props.dispatch(fetchPosts());
26 | }
27 | }, 100);
28 | return (
29 |
30 | {ishome ? (
31 |
32 | {Object.entries(props.homepage.fetchedPosts).map(
33 | ([key, value], index) => {
34 | return (
35 |
36 |
43 |
44 | );
45 | }
46 | )}
47 |
48 | ) : (
49 |
50 | {Object.entries(props.posts).map(([key, value], index) => {
51 | if (items.indexOf(key) !== -1) {
52 | return (
53 |
61 | );
62 | }
63 | })}
64 |
65 | )}
66 |
67 | {props.homepage.loading && (
68 |
73 | )}
74 |
75 |
76 | );
77 | };
78 |
79 | const mapStateToProps = (state, props) => {
80 | return {
81 | auth: state.authenticationReducer,
82 | subinfo: state.postReducer.data,
83 | posts: state.postReducer.posts,
84 | activeSub: state.subredditReducer.activeSubreddit,
85 | data: state.postReducer.data,
86 | homepage: state.postReducer.homepage,
87 | };
88 | };
89 |
90 | export default connect(mapStateToProps)(ContentBox);
91 |
--------------------------------------------------------------------------------
/src/styles/components/followingtabStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { popin } from "./animations";
3 |
4 | export const StyledFollowingTab = styled.div`
5 | width: 100vw;
6 | height: 100vh;
7 | background: rgba(0, 0, 0, 0.4);
8 | background-size: cover;
9 | backdrop-filter: blur(2px);
10 | display: flex;
11 | justify-content: center;
12 | position: fixed;
13 | z-index: 22;
14 | transition: all 1s ease-in-out;
15 | `;
16 |
17 | export const StyledFTab = styled.div`
18 | display: flex;
19 | flex-direction: column;
20 | margin-top: 4rem;
21 | background: ${({ theme }) => theme.bg};
22 | height: 26rem;
23 | width: 20rem;
24 | border: none;
25 | border-radius: 1rem;
26 | box-shadow: 5px 10px 2rem
27 | ${({ theme }) => (theme.name === "light" ? "gray" : "black")};
28 | position: fixed;
29 | top: 8%;
30 | z-index: 12;
31 | transition: all 1s ease-in-out;
32 | animation: ${popin} 0.1s;
33 | `;
34 |
35 | export const StyledFTabHeader = styled.div`
36 | display: flex;
37 | flex-direction: row;
38 | border-radius: 1rem 1rem 0 0;
39 | border-bottom: 1px ${({ theme }) => theme.colors.gray2} solid;
40 | text-align: center;
41 | height: 3rem;
42 | `;
43 |
44 | export const StyledFTabText = styled.p`
45 | width: 90%;
46 | padding-left: 2rem;
47 | `;
48 |
49 | export const StyledFTabCloseBtn = styled.p`
50 | transform: rotate(45deg);
51 | font-size: 2rem;
52 | cursor: pointer;
53 | width: 3rem;
54 | height: 3rem;
55 | margin: 0;
56 | outline: none;
57 | border: none;
58 | `;
59 |
60 | export const StyledFollowings = styled.div`
61 | display: flex;
62 | flex-direction: column;
63 | overflow-y: scroll;
64 | height: 100%;
65 | padding: 1rem;
66 | `;
67 |
68 | export const StyledFollowing = styled.div`
69 | display: flex;
70 | flex-direction: row;
71 | & > .following_details {
72 | cursor: pointer;
73 | display: flex;
74 | width: 100%;
75 | height: 3rem;
76 | padding: 1px 0;
77 | img {
78 | width: 2rem;
79 | height: 2rem;
80 | border: none;
81 | border-radius: 50%;
82 | align-self: center;
83 | }
84 | ,
85 | p {
86 | padding-left: 1rem;
87 | text-decoration: none;
88 | color: ${({ theme }) => theme.color};
89 | text-overflow: ellipsis;
90 | }
91 | }
92 | `;
93 |
94 | export const StyledUnfollowBtn = styled.button`
95 | background: ${({ theme }) => theme.colors.gray7};
96 | color: white;
97 | border: none;
98 | outline: none;
99 | border-radius: 6px;
100 | width: 4rem;
101 | height: 2rem;
102 | cursor: pointer;
103 | margin-top: 0.5rem;
104 | `;
105 |
--------------------------------------------------------------------------------
/src/styles/components/redditorStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledScrollBtn = styled.div`
4 | background: ${({ theme }) => theme.bg};
5 | border: none;
6 | border-radius: 50%;
7 | box-shadow: 1px 2px 5px ${({ theme }) =>
8 | theme.name === "light" ? "gray" : "black"};
9 | cursor: pointer;
10 | font-size: 1rem;
11 | height: 1.7rem;
12 | opacity: .9;
13 | padding-top: .1rem;
14 | position: absolute;
15 | text-align: center;
16 | top: 9rem;
17 | width: 1.7rem;
18 | ${(props) => {
19 | if (props.position === "left") {
20 | return "left: 13.6%;transform: rotate(175deg);";
21 | } else if (props.position === "right") {
22 | return "left: 59%;";
23 | }
24 | }}
25 | @media(max-width: ${({ theme }) => theme.breakpoint}){
26 | top: 4.4rem;
27 | ${(props) => {
28 | if (props.position === "left") {
29 | return "left: 2%;";
30 | } else if (props.position === "right") {
31 | return "left: 90%;";
32 | }
33 | }}
34 | }
35 | `;
36 |
37 | export const StyledRedditor = styled.div`
38 | background: ${({ theme }) => theme.bg};
39 | border: 1px ${({ theme }) => theme.border} solid;
40 | border-radius: 2px;
41 | display: flex;
42 | flex-direction: row;
43 | height: 8rem;
44 | justify-content: flex-start;
45 | margin-top: 4rem;
46 | overflow-x: scroll;
47 | overflow-y: hidden;
48 | scroll-behavior: smooth;
49 | width: 100%;
50 | &::-webkit-scrollbar {
51 | display: none;
52 | }
53 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
54 | background: none;
55 | border: none;
56 | border: 1px ${({ theme }) => theme.border} solid;
57 | height: 7.5rem;
58 | margin: 0;
59 | overflow-y: hidden;
60 | }
61 | `;
62 |
63 | export const StyledSubContainer = styled.div`
64 | align-self: center;
65 | display: flex;
66 | flex-direction: row;
67 | `;
68 |
69 | export const StyledSub = styled.div`
70 | margin: 2rem 0.6rem;
71 | align-self: center;
72 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
73 | padding-top: 1rem;
74 | }
75 | & > .sub__name {
76 | color: ${({ theme }) => theme.colors.gray7};
77 | font-size: 0.9rem;
78 | text-align: center;
79 | margin: 0;
80 | }
81 | & > .sub__name-active {
82 | color: red;
83 | }
84 | `;
85 |
86 | export const StyledSubIcon = styled.img`
87 | border: 2px ${({ theme }) => theme.colors.red} solid;
88 | border-radius: 50%;
89 | cursor: pointer;
90 | height: 4.1rem;
91 | align-self: center;
92 | margin: 0 0.2rem;
93 | outline: none;
94 | padding: 0.2rem;
95 | width: 4.2rem;
96 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
97 | height: 3.7rem;
98 | width: 3.8rem;
99 | }
100 | `;
101 |
--------------------------------------------------------------------------------
/src/styles/components/userStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledUserInfoContainer = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | height: 100%;
7 | left: 65%;
8 | margin-top: 7rem;
9 | position: fixed;
10 | width: 30%;
11 | `;
12 |
13 | export const StyledUserInfo = styled.div`
14 | float: left;
15 | margin-bottom: 1rem;
16 | `;
17 |
18 | export const StyledUserInfoText = styled.div`
19 | float: left;
20 | margin-left: 1rem;
21 | margin-top: 0.4rem;
22 | & > .name {
23 | color: #888;
24 | font-size: 0.8rem;
25 | margin: 0;
26 | margin-top: 0.2rem;
27 | }
28 | & > .username {
29 | color: ${({ theme }) => theme.color};
30 | font-weight: 600;
31 | text-decoration: none;
32 | }
33 | `;
34 |
35 | export const StyledUserName = styled.p`
36 | color: ${({ theme }) => theme.color}
37 | text-decoration: none;
38 | margin: 0;
39 | font-size: .9rem;
40 | font-weight: 600;
41 | `;
42 |
43 | export const StyledUserAvatar = styled.img`
44 | border: none;
45 | border-radius: 50%;
46 | float: left;
47 | height: 3.4rem;
48 | width: 3.4rem;
49 | `;
50 |
51 | export const StyledSuggestions = styled.div`
52 | width: 22rem;
53 | display: flex;
54 | flex-direction: column;
55 | height: 22rem;
56 | margin-bottom: 1rem;
57 | & > b {
58 | color: $gray6;
59 | font-size: 1rem;
60 | font-weight: 500;
61 | margin: 0.4rem 0 0.6rem 0.5rem;
62 | }
63 | `;
64 |
65 | export const StyledSuggestion = styled.div`
66 | display: flex;
67 | flex-direction: row;
68 | justify-content: space-between;
69 | margin: 0.6rem;
70 | align-items: center;
71 | & > div {
72 | height: 3rem;
73 | margin: 0;
74 | margin-left: 1rem;
75 | padding: 0;
76 | }
77 | & > img {
78 | border: none;
79 | border-radius: 50%;
80 | float: left;
81 | height: 2.8rem;
82 | width: 2.8rem;
83 | }
84 | & > button {
85 | font-size: 2.5rem;
86 | display: flex;
87 | right: 10%;
88 | margin-bottom: 4rem;
89 | position: absolute;
90 | }
91 | `;
92 |
93 | export const StyledSuggestionText = styled.div`
94 | width: 2rem;
95 | position: absolute;
96 | left: 4rem;
97 | & > p {
98 | font-size: 1rem;
99 | font-weight: 400;
100 | margin: 0;
101 | }
102 | & > .suggestionTextTitle {
103 | color: ${({ theme }) => theme.color};
104 | text-decoration: none;
105 | font-weight: 600;
106 | }
107 | `;
108 |
109 | export const StyledUserInfoFooter = styled.div`
110 | color: #aaa;
111 | font-size: 0.6rem;
112 | margin-top: 1rem;
113 | text-decoration: none;
114 | & > .footertext {
115 | color: inherit;
116 | font-size: 0.8rem;
117 | margin: 1rem 0.8rem 1rem 0;
118 | text-decoration: none;
119 | }
120 | `;
121 |
--------------------------------------------------------------------------------
/src/components/PostCard.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | StyledExplorePost,
4 | StyledExplorePostImg,
5 | StyledExploreTextPost,
6 | StyledHoverDisplay,
7 | StyledUpvoteIcon,
8 | } from "../styles/components/exploreStyles";
9 |
10 | class PostCard extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | hovering: false,
15 | };
16 | this.MouseEnter = this.MouseEnter.bind(this);
17 | this.MouseLeave = this.MouseLeave.bind(this);
18 | }
19 | MouseEnter = (e) => {
20 | this.setState({ hovering: true });
21 | };
22 | MouseLeave = (e) => {
23 | this.setState({ hovering: false });
24 | };
25 | render() {
26 | let {
27 | id,
28 | url,
29 | caption,
30 | post,
31 | username,
32 | subreddit,
33 | score,
34 | likes,
35 | is_video,
36 | hovering,
37 | } = this.props.value;
38 | const ext = url.substring(url.length - 4, url.length);
39 | let ifimg =
40 | ext === ".jpg" ||
41 | ext === ".png" ||
42 | ext === ".gif" ||
43 | ext === "gifv";
44 | if (!ifimg && url.includes("imgur")) {
45 | url += ".png";
46 | ifimg = true;
47 | }
48 | is_video = url.includes("youtu");
49 | return (
50 | {
53 | this.props.viewPost(this.props.value.id);
54 | }}
55 | onMouseEnter={this.MouseEnter}
56 | onMouseLeave={this.MouseLeave}
57 | >
58 | {this.state.hovering && (
59 |
60 |
61 | {score}
62 |
63 | )}
64 | {ifimg && (
65 |
69 | (e.target.src =
70 | "https://pics.me.me/couldnt-load-image-tap-to-retry-15674211.png")
71 | }
72 | />
73 | )}
74 | {is_video && (
75 |
83 | )}
84 | {!ifimg && !is_video && (
85 |
86 | {caption}
87 | {post.substring(0, 400)}
88 |
89 | {!post && (
90 |
95 | {url}
96 |
97 | )}
98 |
99 |
100 | )}
101 |
102 | );
103 | }
104 | }
105 |
106 | export default PostCard;
107 |
--------------------------------------------------------------------------------
/src/components/Inbox.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "./Header";
3 | import { connect } from "react-redux";
4 | import {
5 | StyledDM,
6 | StyledDMUsers,
7 | StyledDMHead,
8 | StyledDMUserThread,
9 | StyledMessages,
10 | StyledChatHome,
11 | StyledUser,
12 | } from "../styles/components/inboxStyles";
13 | import { StyledBtnImg } from "../styles/components/headerStyles";
14 | import { getSubInfo } from "../actions/api-calls";
15 |
16 | class Inbox extends React.Component {
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | show_chat_for: undefined,
21 | obj: {},
22 | };
23 | }
24 | showChatFor = (id) => {
25 | this.setState({ show_chat_for: id });
26 | };
27 | playground = async (token) => {
28 | let x = undefined;
29 | await getSubInfo(token, x);
30 | };
31 | render() {
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
48 | Direct
49 |
50 |
51 |
52 | Reddit does not allow this yet, I have plan to
53 | implement some type of another chat client here,
54 | until then, this place is deserted ;_;
55 |
56 | {Object.entries(this.state.obj).map(
57 | ([key, val], index) => {
58 | return (
59 |
61 | this.showChatFor(key)
62 | }
63 | >
64 |
68 | {val.name}
69 |
70 | );
71 | }
72 | )}
73 |
74 |
75 |
76 | {!!!this.state.show_chat_for ? (
77 |
78 |
79 | Your Messages
80 | Send private messages to a reddit user
81 |
83 | this.playground(this.props.access_token)
84 | }
85 | >
86 | playground
87 |
88 |
89 | ) : (
90 |
91 | showing chat for {this.state.show_chat_for}
92 |
93 | )}
94 |
95 |
96 |
97 | );
98 | }
99 | }
100 |
101 | const mapStateToProps = (state) => {
102 | return {
103 | access_token: state.authenticationReducer.access_token,
104 | nightmode: state.settingsReducer.nightmode,
105 | };
106 | };
107 |
108 | export default connect(mapStateToProps)(Inbox);
109 |
--------------------------------------------------------------------------------
/src/components/Notificationbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import {
4 | StyledNotificationBar,
5 | StyledNotificationBarContainer,
6 | StyledNotification,
7 | StyledNFooter,
8 | } from "../styles/components/notificationbarStyles";
9 | import { StyledBtnImg } from "../styles/components/headerStyles";
10 | import { StyledLoader } from "../styles/components/profileStyles";
11 |
12 | class Notificationbar extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | notifications: [],
17 | };
18 | }
19 | componentDidMount() {
20 | return fetch(`https://oauth.reddit.com/message/inbox`, {
21 | method: "GET",
22 | headers: {
23 | Authorization: `bearer ${this.props.token}`,
24 | },
25 | })
26 | .then((res) => res.json())
27 | .then((json) => {
28 | const after = json.data.after;
29 | let notifications = json.data.children.map((child) => {
30 | const {
31 | author,
32 | body,
33 | created,
34 | id,
35 | link_title,
36 | num_comments,
37 | subreddit_name_prefixed: subreddit,
38 | subject,
39 | } = child.data;
40 | const obj = {
41 | author,
42 | body,
43 | created,
44 | id,
45 | link_title,
46 | num_comments,
47 | subreddit,
48 | subject,
49 | };
50 | return obj;
51 | });
52 | this.setState({ notifications });
53 | })
54 | .catch((err) => {
55 | console.log("something went wrong", err);
56 | });
57 | }
58 | render() {
59 | return (
60 |
61 |
62 | {this.state.notifications.length === 0 && (
63 |
64 | )}
65 | {this.state.notifications.map((notification) => {
66 | return (
67 |
68 |
69 | {notification.author}
70 | {notification.subreddit && (
71 | ({notification.subreddit})
72 | )}
73 |
74 |
75 | {notification.subject}
76 | {notification.num_comments && (
77 |
78 |
87 | {notification.num_comments}
88 |
89 | )}
90 |
91 | {moment
92 | .unix(notification.created)
93 | .format("DD/MM HH:mm")}
94 |
95 |
96 |
97 | );
98 | })}
99 |
100 |
101 | );
102 | }
103 | }
104 |
105 | export default Notificationbar;
106 |
--------------------------------------------------------------------------------
/src/components/User.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import _ from "lodash";
5 | import {
6 | StyledUserInfoContainer,
7 | StyledUserInfo,
8 | StyledUserInfoText,
9 | StyledUserAvatar,
10 | StyledUserName,
11 | StyledSuggestions,
12 | StyledSuggestion,
13 | StyledSuggestionText,
14 | StyledUserInfoFooter,
15 | } from "../styles/components/userStyles";
16 | import { StyledAddBtn } from "../styles/components/searchbarStyles";
17 |
18 | //hardcoded suggestions
19 | const subs = [
20 | "memes",
21 | "history",
22 | "dankmemes",
23 | "redditdev",
24 | "javascript",
25 | "news",
26 | "pewdiepiesubmissions",
27 | "videos",
28 | ];
29 |
30 | class User extends React.Component {
31 | constructor(props) {
32 | super(props);
33 | this.state = {
34 | suggestions: {},
35 | };
36 | }
37 | getObj = (data) => {
38 | const { id, display_name: title, icon_img, subscribers } = data;
39 | let obj = {};
40 | obj[id] = {
41 | id,
42 | title,
43 | icon_img,
44 | subscribers,
45 | };
46 | return obj;
47 | };
48 | componentDidMount() {
49 | const that = this;
50 | _.shuffle(subs)
51 | .slice(0, 5)
52 | .map((sub) => {
53 | fetch(`https://www.reddit.com/r/${sub}/about/.json`)
54 | .then((res) => res.json())
55 | .then((json) =>
56 | that.setState({
57 | suggestions: {
58 | ...that.getObj(json.data),
59 | ...that.state.suggestions,
60 | },
61 | })
62 | );
63 | }, that);
64 | }
65 | render() {
66 | const that = this;
67 | return (
68 |
69 |
70 |
74 |
75 |
76 | u/{this.props.user.name}
77 |
78 | {this.props.user.name}
79 |
80 |
81 |
82 | Suggestions For You
83 | {Object.entries(this.state.suggestions).map(
84 | ([key, value], index) => {
85 | const { id, title, icon_img, subscribers } = value;
86 | return (
87 |
88 |
89 |
90 |
94 | {title}
95 |
96 | {subscribers}
97 |
98 |
99 | );
100 | },
101 | that
102 | )}
103 |
104 |
105 |
106 | About
107 |
108 |
113 | Github
114 |
115 | © REDDIT FROM PARALLEL TIMELINE
116 |
117 |
118 | );
119 | }
120 | }
121 |
122 | const mapStateToProps = (state) => {
123 | return {
124 | user: state.authenticationReducer.user,
125 | };
126 | };
127 |
128 | export default connect(mapStateToProps)(User);
129 |
--------------------------------------------------------------------------------
/src/styles/components/viewPostStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { popin, pop } from "./animations";
3 |
4 | export const StyledViewPostContent = styled.div`
5 | background: ${({ theme }) => theme.bg};
6 | width: 35%;
7 | height: 90%;
8 | word-wrap: break-word;
9 | overflow: hidden;
10 | margin: 2rem 0 2rem 2rem;
11 | img {
12 | width: 100%;
13 | height: 100%;
14 | display: flex;
15 | justify-content: space-around;
16 | }
17 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
18 | margin: 0;
19 | padding: 0;
20 | height: 55%;
21 | width: 90%;
22 | border-radius: 0.3rem 0.3rem 0 0;
23 | transition: all 1s ease-in-out;
24 | animation: ${pop} 0.3s ease-in-out;
25 | }
26 | `;
27 |
28 | export const StyledViewPostDetails = styled.div`
29 | display: flex;
30 | flex-direction: column;
31 | background: ${({ theme }) => theme.bg};
32 | height: 90%;
33 | width: 30%;
34 | border-left: 1px ${({ theme }) => theme.themeborder} solid;
35 | margin: 2rem 2rem 2rem 0;
36 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
37 | display: none;
38 | }
39 | `;
40 |
41 | export const StyledPostHead = styled.div`
42 | display: none;
43 | flex-direction: row;
44 | justify-content: space-between;
45 | align-items: center;
46 | background: ${({ theme }) =>
47 | (theme.name === "light" && theme.bg) || theme.bodybg};
48 | color: ${({ theme }) => theme.color}
49 | width: 100%;
50 | height: 3.7rem;
51 | border-radius: .3rem .3rem 0 0;
52 | border-bottom: 1px solid ${({ theme }) => theme.themeborder};
53 | @media(max-width: ${({ theme }) => theme.breakpoint}){
54 | display: flex;
55 | }
56 | `;
57 |
58 | export const StyledCommentSection = styled.div`
59 | overflow-y: scroll;
60 | overflow-x: wrap;
61 | text-align: left;
62 | padding: 1rem;
63 | margin-bottom: 8rem;
64 | &::-webkit-scrollbar {
65 | width: 0px;
66 | }
67 | `;
68 |
69 | export const StyledDetailSection = styled.div`
70 | min-height: 4rem;
71 | display: flex;
72 | flex-direction: row;
73 | align-items: center;
74 | padding-left: 2rem;
75 | border: none;
76 | border-bottom: 0.6px ${({ theme }) => theme.themeborder} solid;
77 | & > .details__username {
78 | text-decoration: none;
79 | font-weight: 600;
80 | color: ${({ theme }) => (theme.name === "light" && "black") || "white"};
81 | }
82 | `;
83 |
84 | export const StyledDetailsFooter = styled.div`
85 | border: none;
86 | border-top: 0.6px ${({ theme }) => theme.themeborder} solid;
87 | display: flex;
88 | flex-direction: column;
89 | background: ${({ theme }) => theme.bg};
90 | position: fixed;
91 | bottom: 5%;
92 | align-items: flex-start;
93 | padding: 1rem;
94 | width: inherit;
95 | & > .footerbtns {
96 | display: flex;
97 | flex-direction: row;
98 | }
99 | & > b {
100 | margin: 0.6rem 0;
101 | }
102 | `;
103 |
104 | export const StyledVPTextPost = styled.div`
105 | display: flex;
106 | background: ${({ theme }) => theme.bg};
107 | height: 100%;
108 | padding: 2rem;
109 | text-align: left;
110 | flex-direction: column;
111 | overflow-wrap: break-word;
112 | overflow-y: scroll;
113 | text-align: justify;
114 | &::-webkit-scrollbar {
115 | width: 8px;
116 | }
117 | `;
118 |
119 | export const StyledVPCloseBtn = styled.div`
120 | position: fixed;
121 | font-size: 3rem;
122 | color: white;
123 | cursor: pointer;
124 | right: 2%;
125 | top: -2%;
126 | transform: rotate(45deg);
127 | `;
128 |
129 | export const StyledFollowBtn = styled.div`
130 | background: none;
131 | cursor: pointer;
132 | color: ${({ theme }) => theme.colors.blue};
133 | margin-left: 1rem;
134 | `;
135 |
--------------------------------------------------------------------------------
/src/components/Explore.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Header from "./Header";
3 | import Searchbar from "./Searchbar";
4 | import { getobj } from "../actions/";
5 | import PostCard from "./PostCard";
6 | import Menubar from "./Menubar";
7 | import ViewPost from "./ViewPost";
8 | import debounce from "lodash.debounce";
9 | import { StyledExplorer } from "../styles/components/exploreStyles";
10 | import { StyledLoader } from "../styles/components/profileStyles";
11 |
12 | class Explore extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | fetchedPosts: {},
17 | after: undefined,
18 | loading: false,
19 | currPostid: undefined,
20 | viewPost: false,
21 | window_width: window.innerWidth,
22 | };
23 |
24 | window.onscroll = debounce(() => {
25 | if (
26 | window.innerHeight + document.documentElement.scrollTop ===
27 | document.documentElement.scrollHeight
28 | ) {
29 | this.loadposts();
30 | }
31 | }, 100);
32 |
33 | this.updateWidth = this.updateWidth.bind(this);
34 | this.viewPost = this.viewPost.bind(this);
35 | this.hidepost = this.hidepost.bind(this);
36 | this.handleChanges = this.handleChanges.bind(this);
37 | }
38 | componentDidMount() {
39 | this.loadposts();
40 | }
41 | updateWidth() {
42 | this.setState({ window_width: window.innerWidth });
43 | }
44 | loadposts() {
45 | this.setState({ loading: true });
46 | let posts = {};
47 | fetch(`https://www.reddit.com/.json?limit=60&after=${this.state.after}`)
48 | .then((res) => res.json())
49 | .then((json) => {
50 | this.setState({ after: json.data.after });
51 | json.data.children.map((child) =>
52 | Object.assign(posts, getobj(child.data))
53 | );
54 | })
55 | .then((now) =>
56 | this.setState({
57 | fetchedPosts: { ...this.state.fetchedPosts, ...posts },
58 | loading: false,
59 | })
60 | );
61 | }
62 | viewPost(id) {
63 | this.setState({ viewPost: true, currPostid: id });
64 | }
65 | hidepost(e) {
66 | this.setState({ viewPost: false, currPost: {} });
67 | }
68 | handleChanges(id, score, likes) {
69 | this.setState((prevState) => ({
70 | fetchedPosts: {
71 | ...prevState.fetchedPosts,
72 | [id]: {
73 | ...prevState.fetchedPosts[id],
74 | score,
75 | likes,
76 | },
77 | },
78 | }));
79 | }
80 | render() {
81 | window.addEventListener("resize", this.updateWidth);
82 | return (
83 |
84 | {this.state.window_width >= 740 ? (
85 |
86 | ) : (
87 |
88 | )}
89 | {this.state.viewPost && (
90 |
95 | )}
96 |
97 | {Object.entries(this.state.fetchedPosts)
98 | .filter(([key, value], index) => {
99 | return !value.is_video;
100 | })
101 | .map(([key, value], index) => {
102 | if (value) {
103 | return (
104 |
110 | );
111 | } else {
112 | return
;
113 | }
114 | })}
115 |
116 | {this.state.loading && (
117 |
122 | )}
123 |
124 |
125 |
126 |
127 | );
128 | }
129 | }
130 |
131 | export default Explore;
132 |
--------------------------------------------------------------------------------
/src/components/Notifications.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import moment from "moment";
3 | import { connect } from "react-redux";
4 | import {
5 | StyledPageHeading,
6 | StyledPageCanvas,
7 | Styledbody,
8 | } from "../styles/components/pageStyles";
9 | import { StyledBtnImg } from "../styles/components/headerStyles";
10 | import {
11 | StyledNotification,
12 | StyledNFooter,
13 | } from "../styles/components/notificationbarStyles";
14 | import Menubar from "./Menubar";
15 |
16 | class Notifications extends React.Component {
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | notifications: [],
21 | };
22 | }
23 | componentDidMount() {
24 | return fetch(`https://oauth.reddit.com/message/inbox`, {
25 | method: "GET",
26 | headers: {
27 | Authorization: `bearer ${this.props.token}`,
28 | },
29 | })
30 | .then((res) => res.json())
31 | .then((json) => {
32 | const after = json.data.after;
33 | let notifications = json.data.children.map((child) => {
34 | const {
35 | author,
36 | body,
37 | created,
38 | id,
39 | link_title,
40 | num_comments,
41 | subreddit_name_prefixed: subreddit,
42 | subject,
43 | } = child.data;
44 | const obj = {
45 | author,
46 | body,
47 | created,
48 | id,
49 | link_title,
50 | num_comments,
51 | subreddit,
52 | subject,
53 | };
54 | return obj;
55 | });
56 | this.setState({ notifications });
57 | })
58 | .catch((err) => {
59 | console.log("something went wrong", err);
60 | });
61 | }
62 | render() {
63 | return (
64 |
65 |
66 | Activity
67 |
68 | {(this.props.authenticated && (
69 |
72 | {this.state.notifications.map((notification) => {
73 | return (
74 |
75 |
76 | {notification.author}
77 | {notification.subreddit && (
78 |
79 | ({notification.subreddit})
80 |
81 | )}
82 |
83 |
84 | {notification.subject}
85 | {notification.num_comments && (
86 |
87 |
96 | {notification.num_comments}
97 |
98 | )}
99 |
100 | {moment
101 | .unix(notification.created)
102 | .format("DD/MM HH:mm")}
103 |
104 |
105 |
106 | );
107 | })}
108 |
109 | )) || (
110 |
117 | You are not logged in
118 |
119 | )}
120 |
121 |
122 | );
123 | }
124 | }
125 |
126 | const mapStateToProps = (state) => {
127 | return {
128 | authenticated: state.authenticationReducer.authenticated,
129 | token: state.authenticationReducer.access_token,
130 | nightmode: state.settingsReducer.nightmode,
131 | };
132 | };
133 |
134 | export default connect(mapStateToProps)(Notifications);
135 |
--------------------------------------------------------------------------------
/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { NavLink } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import { deselect_subreddit } from "../actions";
5 | import Searchbar from "./Searchbar";
6 | import Notificationbar from "./Notificationbar";
7 | import {
8 | StyledHeader,
9 | StyledHeaderBtns,
10 | StyledBtnImg,
11 | } from "../styles/components/headerStyles";
12 |
13 | class Header extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | showNotification: false,
18 | };
19 | this.wrapperRef = React.createRef();
20 | }
21 | handleClickOutside = (event) => {
22 | const that = this;
23 | if (
24 | this.wrapperRef &&
25 | this.wrapperRef.current &&
26 | !this.wrapperRef.current.contains(event.target)
27 | ) {
28 | setTimeout(
29 | function () {
30 | that.setState({ showNotification: false });
31 | },
32 | 100,
33 | that
34 | );
35 | } else {
36 | this.setState({ showNotification: !this.state.showNotification });
37 | }
38 | };
39 | componentDidMount() {
40 | document.addEventListener("mousedown", this.handleClickOutside);
41 | }
42 | render() {
43 | return (
44 |
45 |
46 |
47 |
49 | this.props.dispatch(deselect_subreddit())
50 | }
51 | >
52 | @reddit
53 |
54 |
55 |
60 |
61 |
62 |
64 | this.props.dispatch(deselect_subreddit())
65 | }
66 | type="home"
67 | src={
68 | this.props.nightmode
69 | ? "/images/home-light.png"
70 | : "/images/home.png"
71 | }
72 | alt="home"
73 | />
74 |
75 | {this.props.isAuthenticated && (
76 |
77 |
86 |
87 | )}
88 |
89 |
98 |
99 |
110 |
111 |
112 |
113 |
114 |
115 | {this.props.isAuthenticated &&
116 | this.props.hasOwnProperty("dashboard") && (
117 |
118 |
127 |
128 | )}
129 | {this.state.showNotification && (
130 |
131 | )}
132 |
133 | );
134 | }
135 | }
136 |
137 | const mapStateToProps = (state, props) => {
138 | return {
139 | icon_img: state.authenticationReducer.user.icon_img,
140 | isAuthenticated: state.authenticationReducer.authenticated,
141 | access_token: state.authenticationReducer.access_token,
142 | nightmode: state.settingsReducer.nightmode,
143 | };
144 | };
145 |
146 | export default connect(mapStateToProps)(Header);
147 |
--------------------------------------------------------------------------------
/src/reducers/postReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | REQUEST_POSTS,
3 | RECEIVE_POSTS,
4 | ADD_SUBREDDIT,
5 | ADD_SUBREDDITS,
6 | HOMEPAGE,
7 | HOMEPAGEPOST_UPVOTE,
8 | HOMEPAGEPOST_DOWNVOTE,
9 | UPVOTE,
10 | DOWNVOTE,
11 | LOAD_HOMEPAGE,
12 | LOGGED_IN,
13 | LOGOUT,
14 | REMOVE_SUBREDDIT,
15 | } from "../actions/action-types";
16 | import _ from "lodash";
17 |
18 | const initialState = {
19 | data: {},
20 | posts: {},
21 | homepage: {
22 | loading: false,
23 | fetchedPosts: {},
24 | after: undefined,
25 | lastUpdated: undefined,
26 | },
27 | };
28 |
29 | const postReducer = (state = initialState, action) => {
30 | switch (action.type) {
31 | case REQUEST_POSTS:
32 | return {
33 | ...state,
34 | data: {
35 | ...state.data,
36 | [action.payload.subreddit]: {
37 | ...state.data[action.payload.subreddit],
38 | fetching: true,
39 | },
40 | },
41 | };
42 | case RECEIVE_POSTS:
43 | let {
44 | subreddit,
45 | items,
46 | posts: fetchedPosts,
47 | after,
48 | lastUpdated,
49 | } = action.payload;
50 | let { data, posts } = state;
51 | posts = { ...posts, ...fetchedPosts };
52 | posts = _.shuffle(posts);
53 | posts = _.keyBy(posts, "id");
54 | return {
55 | ...state,
56 | data: {
57 | ...data,
58 | [subreddit]: {
59 | ...state.data[subreddit],
60 | fetching: false,
61 | items,
62 | after,
63 | lastUpdated,
64 | },
65 | },
66 | posts,
67 | };
68 | case LOAD_HOMEPAGE: {
69 | return {
70 | ...state,
71 | homepage: {
72 | ...state.homepage,
73 | loading: true,
74 | },
75 | };
76 | }
77 | case HOMEPAGE: {
78 | let { fetchedPosts, after, lastUpdated } = action.payload;
79 | return {
80 | ...state,
81 | homepage: {
82 | loading: false,
83 | fetchedPosts: {
84 | ...state.homepage.fetchedPosts,
85 | ...fetchedPosts,
86 | },
87 | after,
88 | lastUpdated,
89 | },
90 | };
91 | }
92 | case HOMEPAGEPOST_UPVOTE: {
93 | let { id, likes, score } = action.payload;
94 | return {
95 | ...state,
96 | homepage: {
97 | ...state.homepage,
98 | fetchedPosts: {
99 | ...state.homepage.fetchedPosts,
100 | [id]: {
101 | ...state.homepage.fetchedPosts[id],
102 | likes,
103 | score,
104 | },
105 | },
106 | },
107 | };
108 | }
109 | case HOMEPAGEPOST_DOWNVOTE: {
110 | let { id, likes, score } = action.payload;
111 | return {
112 | ...state,
113 | homepage: {
114 | ...state.homepage,
115 | fetchedPosts: {
116 | ...state.homepage.fetchedPosts,
117 | [id]: {
118 | ...state.homepage.fetchedPosts[id],
119 | likes,
120 | score,
121 | },
122 | },
123 | },
124 | };
125 | }
126 | case ADD_SUBREDDIT:
127 | return {
128 | ...state,
129 | data: {
130 | ...state.data,
131 | [action.payload.subreddit]: {
132 | fetching: false,
133 | items: [],
134 | icon: action.payload.icon,
135 | after: undefined,
136 | lastUpdated: undefined,
137 | },
138 | },
139 | };
140 | case ADD_SUBREDDITS:
141 | return {
142 | ...state,
143 | data: {
144 | ...state.data,
145 | ...action.payload.subdata,
146 | },
147 | };
148 | case REMOVE_SUBREDDIT: {
149 | const subreddit = action.payload.subreddit;
150 | let items = state.data[subreddit].items;
151 | let posts = state.posts;
152 | Object.keys(posts).map((key) => {
153 | if (items.includes(key)) {
154 | delete posts[key];
155 | }
156 | });
157 | let data = state.data;
158 | delete data[action.payload.subreddit];
159 | return {
160 | ...state,
161 | data: { ...data },
162 | posts: posts,
163 | };
164 | }
165 | case LOGGED_IN: {
166 | return {
167 | ...initialState,
168 | };
169 | }
170 | case LOGOUT: {
171 | return {
172 | ...initialState,
173 | };
174 | }
175 | case UPVOTE: {
176 | return {
177 | ...state,
178 | posts: {
179 | ...state.posts,
180 | [action.payload.id]: {
181 | ...state.posts[action.payload.id],
182 | score: action.payload.score,
183 | likes: action.payload.likes,
184 | },
185 | },
186 | };
187 | }
188 | case DOWNVOTE: {
189 | return {
190 | ...state,
191 | posts: {
192 | ...state.posts,
193 | [action.payload.id]: {
194 | ...state.posts[action.payload.id],
195 | score: action.payload.score,
196 | likes: action.payload.likes,
197 | },
198 | },
199 | };
200 | }
201 | default:
202 | return state;
203 | }
204 | };
205 |
206 | export default postReducer;
207 |
--------------------------------------------------------------------------------
/src/styles/components/profileStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { FadeInRight } from "./animations";
3 |
4 | export const StyledProfile = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | width: 60%;
8 | position: absolute;
9 | margin: 0 20%;
10 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
11 | margin: 0;
12 | width: 100vw;
13 | overflow: hidden;
14 | }
15 | animation: ${FadeInRight} 0.3s ease;
16 | `;
17 |
18 | export const StyledProfileInfo = styled.div`
19 | display: inherit;
20 | flex-direction: row;
21 | border-bottom: 1px ${({ theme }) => theme.colors.gray3} solid;
22 | padding: 6rem 0;
23 | float: left;
24 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
25 | overflow-wrap: break-word;
26 | padding-top: 3rem;
27 | }
28 | `;
29 |
30 | export const StyledProfileName = styled.p`
31 | margin: 0;
32 | margin-right: 1rem;
33 | font-size: 1.8rem;
34 | font-weight: 80;
35 | `;
36 |
37 | export const StyledProfileDetails = styled.div`
38 | display: inherit;
39 | flex-direction: column;
40 | position: relative;
41 | top: 1rem;
42 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
43 | left: 1rem;
44 | width: 70%;
45 | }
46 | & > .userdetails {
47 | display: flex;
48 | flex-direction: row;
49 | justify-content: flex-start;
50 | overflow-wrap: break-word;
51 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
52 | flex-direction: column;
53 | }
54 | }
55 | & > .subbtn {
56 | display: flex;
57 | flex-direction: row;
58 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
59 | margin: 0;
60 | flex-direction: column;
61 | }
62 | }
63 | & > p {
64 | width: 90%;
65 | text-align: justify;
66 | overflow-wrap: break-word;
67 | }
68 | `;
69 |
70 | export const StyledButtonGroup = styled.div`
71 | display: flex;
72 | flex-direction: row;
73 | `;
74 |
75 | export const StyledProfilePosts = styled.div`
76 | display: flex;
77 | justify-content: center;
78 | margin-top: 1rem;
79 | `;
80 |
81 | export const StyledProfileIcon = styled.img`
82 | border: none;
83 | border-radius: 50%;
84 | width: 9rem;
85 | height: 9em;
86 | padding: 1rem;
87 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
88 | padding: 0;
89 | width: 5rem;
90 | height: 5rem;
91 | margin-top: 1.2rem;
92 | }
93 | `;
94 |
95 | export const StyledProfileGallery = styled.div`
96 | display: flex;
97 | flex-flow: row wrap;
98 | justify-content: flex-start;
99 | `;
100 |
101 | export const StyledButton = styled.div`
102 | display: flex;
103 | align-self: center;
104 | justify-content: center;
105 | padding-top: .2rem;
106 | position: relative;
107 | width: 5rem;
108 | height: 2rem;
109 | border: none;
110 | outline: none;
111 | border-radius: 4px;
112 | font-weight: 550;
113 | cursor: pointer;
114 | ${(props) => {
115 | switch (props.type) {
116 | case "primary":
117 | return "background: #0075f6;color: white;";
118 | case "secondary":
119 | return "background: gray; color: white;";
120 | }
121 | }}
122 | @media(max-width: ${({ theme }) => theme.breakpoint}){
123 | width: 8rem;
124 | align-self: flex-start;
125 | margin: .2rem 0;
126 | }
127 | `;
128 |
129 | export const StyledLoader = styled.img`
130 | position: fixed;
131 | width: 3rem;
132 | display: flex;
133 | align-self: center;
134 | ${(props) => {
135 | switch (props.size) {
136 | case "sm":
137 | return "width:1rem;height:1rem;";
138 | case "mid":
139 | return "height: 3rem;margin-bottom: 3rem;";
140 | }
141 | }}
142 | ${(props) => {
143 | switch (props.type) {
144 | case "input":
145 | return `
146 | top: 1.1rem;
147 | height: 1rem;
148 | right: 44.5%;
149 | position: absolute;
150 | width: 1rem;
151 | z-index: 6;
152 | @media(max-width: ${props.theme.breakpoint}){
153 | top: 1%;
154 | right: 2%;
155 | }`;
156 | case "pageload":
157 | return `
158 | bottom: 0%;
159 | right: 48%;
160 | @media(max-width: ${props.theme.breakpoint}){
161 | right: 44%;
162 | }`;
163 | default:
164 | return "position: default;margin: 1rem;";
165 | }
166 | }}
167 | `;
168 |
169 | export const StyledLoginBoard = styled.div`
170 | display: flex;
171 | flex-direction: column;
172 | align-items: center;
173 | align-self: center;
174 | padding-top: 4rem;
175 | & > span {
176 | display: flex;
177 | flex-direction: column;
178 | justify-content: center;
179 | align-items: center;
180 | align-self: center;
181 | transition: all 0.1s ease;
182 | }
183 | transition: all 0.1s ease;
184 | `;
185 |
186 | export const StyledInfoBlock = styled.div`
187 | display: flex;
188 | flex-direction: row;
189 | align-items: center;
190 | `;
191 |
192 | export const StyledInlineDiv = styled.div`
193 | display: flex;
194 | align-items: center;
195 | & > span {
196 | font-weight: bold;
197 | margin-right: 2rem;
198 | }
199 | `;
200 |
--------------------------------------------------------------------------------
/src/components/Searchbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { withRouter } from "react-router-dom";
4 | import { history } from "../router/AppRouter";
5 | import { select_subreddit, addSubreddit } from "../actions";
6 | import { sub } from "../actions/api-calls";
7 | import styled from "styled-components";
8 | import {
9 | StyledSearchbar,
10 | StyledSearchInput,
11 | StyledSearchIcon,
12 | StyledSearchResults,
13 | StyledClearBtn,
14 | StyledAddBtn,
15 | StyledSearchedSub,
16 | StyledSearchedSubContainer,
17 | StyledSearchedResultsArrow,
18 | } from "../styles/components/searchbarStyles";
19 | import { StyledLoader } from "../styles/components/profileStyles";
20 |
21 | let timeout = null;
22 |
23 | class Searchbar extends React.Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | focus: false,
28 | subs: [],
29 | value: "",
30 | loading: false,
31 | };
32 | }
33 | onChangeHandler = (e) => {
34 | this.setState({ focus: true, value: e.target.value, loading: true });
35 | clearTimeout(timeout);
36 | let value = e.target.value;
37 | timeout = setTimeout(
38 | function (query, that) {
39 | let obj = {};
40 | fetch(`https://www.reddit.com/search.json?q=${query}&type=sr`)
41 | .then((res) => res.json())
42 | .then((json) =>
43 | json.data.children.map((sub) => {
44 | const {
45 | id,
46 | display_name: name,
47 | title,
48 | icon_img,
49 | subscribers,
50 | public_description: description,
51 | } = sub.data;
52 | obj = {
53 | id,
54 | name,
55 | title,
56 | icon_img,
57 | subscribers,
58 | description,
59 | };
60 | return obj;
61 | })
62 | )
63 | .then((subs) => that.setState({ subs, loading: false }))
64 | .catch((err) => {
65 | that.setState({ loading: false });
66 | });
67 | },
68 | 1000,
69 | value,
70 | this
71 | );
72 | };
73 | onFocusHandler = () => {
74 | this.setState({ focus: true });
75 | };
76 | onBlurHandler = (e) => {
77 | setTimeout(() => {
78 | this.setState({ focus: false });
79 | }, 1000);
80 | };
81 | addSub = (sub) => {
82 | this.setState({ value: "", subs: [] });
83 | this.props.dispatch(addSubreddit(sub.slice(2)));
84 | if (this.props.authenticated) {
85 | sub(this.props.access_token, this.state.subdetails.full_name);
86 | }
87 | };
88 | clear = () => {
89 | this.setState({ value: "", subs: [] });
90 | };
91 | render() {
92 | return (
93 |
94 |
104 |
108 | 🔍︎
109 |
110 | {this.state.focus && !!this.state.value.length && (
111 |
112 | {this.state.loading ? (
113 |
118 | ) : (
119 |
120 | +
121 |
122 | )}
123 |
124 | )}
125 | {this.state.focus && this.state.subs.length > 0 && (
126 |
127 |
128 | {this.state.subs.map((sub) => {
129 | let icon = "";
130 | {
131 | sub.icon_img === ""
132 | ? (icon = "/images/icon.png")
133 | : (icon = sub.icon_img);
134 | }
135 | return (
136 |
137 | {
139 | this.setState({
140 | value: "",
141 | subs: [],
142 | });
143 | this.props.history.push(
144 | `/r/${sub.name}`
145 | );
146 | }}
147 | name={sub.name}
148 | key={sub.id}
149 | >
150 |
151 | r/{sub.name}
152 | {sub.title}
153 |
154 | {!this.props.subreddits.includes(
155 | sub.name
156 | ) && (
157 |
160 | this.addSub(`r/${sub.name}`)
161 | }
162 | >
163 | +
164 |
165 | )}
166 |
167 | );
168 | })}
169 |
170 |
171 | )}
172 |
173 | );
174 | }
175 | }
176 |
177 | const mapStateToProps = (state) => {
178 | return {
179 | subreddits: state.subredditReducer.subreddits,
180 | };
181 | };
182 |
183 | export default withRouter(connect(mapStateToProps)(Searchbar));
184 |
--------------------------------------------------------------------------------
/src/actions/api-calls.js:
--------------------------------------------------------------------------------
1 | export const getUserInfo = (token) => {
2 | return fetch(`https://oauth.reddit.com/api/v1/me`, {
3 | method: "GET",
4 | headers: {
5 | Authorization: `bearer ${token}`,
6 | },
7 | })
8 | .then((res) => res.json())
9 | .then((json) => {
10 | let {
11 | icon_img,
12 | id,
13 | name,
14 | num_friends,
15 | pref_nightmode,
16 | total_karma,
17 | } = json;
18 | icon_img = /(.*)\?/.exec(icon_img)[1];
19 | const userinfo = {
20 | icon_img,
21 | id,
22 | name,
23 | num_friends,
24 | pref_nightmode,
25 | total_karma,
26 | };
27 | return userinfo;
28 | })
29 | .catch((err) => {
30 | console.log("something went wrong", err);
31 | });
32 | };
33 |
34 | export const getSubInfo = async (token, after = undefined) => {
35 | return await fetch(
36 | `https://oauth.reddit.com/subreddits/mine/subscriber?after=${after}&limit=100`,
37 | {
38 | method: "GET",
39 | headers: {
40 | Authorization: `bearer ${token}`,
41 | },
42 | }
43 | )
44 | .then((res) => res.json())
45 | .then((json) => {
46 | const after = json.data.after;
47 | const subs = json.data.children.map((child) => {
48 | const {
49 | created,
50 | description,
51 | icon_img: icon,
52 | display_name: name,
53 | id,
54 | subscribers,
55 | title,
56 | } = child.data;
57 | const sub = {
58 | created,
59 | description,
60 | icon,
61 | name,
62 | id,
63 | subscribers,
64 | title,
65 | };
66 | if (!sub.icon) sub.icon = "/images/icon.png";
67 | return sub;
68 | });
69 | return { after, subs };
70 | })
71 | .then(async (obj) => {
72 | if (obj.subs.length < 100) {
73 | return obj;
74 | } else {
75 | let x = await getSubInfo(token, obj.after);
76 | let subs = [...x.subs, ...obj.subs];
77 | const after = x.after;
78 | return { subs, after };
79 | }
80 | })
81 | .catch((err) => {
82 | console.log("error while fetching subs, ", err);
83 | });
84 | };
85 |
86 | export const sub = (token, full_name) => {
87 | fetch(`https://oauth.reddit.com/api/subscribe`, {
88 | method: "POST",
89 | headers: {
90 | Authorization: `bearer ${token}`,
91 | "Content-Type": "application/x-www-form-urlencoded",
92 | skip_initial_defaults: "1",
93 | },
94 | body: `action=sub&sr=${full_name}`,
95 | }).catch((err) => {
96 | console.log("sub req. failed... cannot join this subreddit", err);
97 | });
98 | };
99 |
100 | export const unsub = (token, full_name) => {
101 | fetch(`https://oauth.reddit.com/api/subscribe`, {
102 | method: "POST",
103 | headers: {
104 | Authorization: `bearer ${token}`,
105 | "Content-Type": "application/x-www-form-urlencoded",
106 | },
107 | body: `action=unsub&sr=${full_name}`,
108 | }).catch((err) => {
109 | console.log("unsub req. failed... cannot leave this subreddit", err);
110 | });
111 | };
112 |
113 | export const getTrophies = (token) => {
114 | return fetch(`https://oauth.reddit.com/api/v1/me/trophies`, {
115 | method: "GET",
116 | headers: {
117 | Authorization: `bearer ${token}`,
118 | },
119 | })
120 | .then((res) => res.json())
121 | .then((json) => {
122 | let data = {};
123 | json.data.trophies.map((trophy, index) => {
124 | data[index] = {
125 | icon: trophy.data.icon_70,
126 | name: trophy.data.name,
127 | };
128 | });
129 | return data;
130 | });
131 | };
132 |
133 | export const deleteCall = (token, id) => {
134 | fetch(`https://oauth.reddit.com/api/del`, {
135 | method: "POST",
136 | headers: {
137 | Authorization: `bearer ${token}`,
138 | "Content-Type": "application/x-www-form-urlencoded",
139 | skip_initial_defaults: "1",
140 | },
141 | body: `id=${id}`,
142 | }).catch((err) => {
143 | console.log("delete req. failed", err);
144 | });
145 | };
146 |
147 | export const editText = async (token, id, comment) => {
148 | await fetch(`https://oauth.reddit.com/api/editusertext`, {
149 | method: "POST",
150 | headers: {
151 | Authorization: `bearer ${token}`,
152 | "Content-Type": "application/x-www-form-urlencoded",
153 | },
154 | body: `api_type=json&return_rtjson=true&text=${comment}&thing_id=${id}`,
155 | })
156 | .then((res) => console.log(res))
157 | .catch((err) => {
158 | console.log("err with text editing", err);
159 | });
160 | };
161 |
162 | export const refreshData = async (access_token, refresh_token) => {
163 | const clientID = process.env.REACT_APP_CLIENTID;
164 | const clientSecret = process.env.REACT_APP_CLIENTSECRET;
165 | const encode = btoa(
166 | `${process.env.REACT_APP_CLIENTID}:${process.env.REACT_APP_CLIENTSECRET}`
167 | );
168 | return await fetch("https://www.reddit.com/api/v1/access_token", {
169 | method: "POST",
170 | body: `grant_type=refresh_token&refresh_token=${refresh_token}&duration=permanent`,
171 | headers: {
172 | "Content-Type": "application/x-www-form-urlencoded",
173 | Authorization: `basic ${encode}`,
174 | },
175 | })
176 | .then((res) => res.json())
177 | .then((data) => {
178 | data["refresh_token"] = refresh_token;
179 | return data;
180 | })
181 | .catch((error) => {
182 | console.log("error occured while refreshing the token", error);
183 | });
184 | };
185 |
--------------------------------------------------------------------------------
/src/styles/components/searchbarStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const StyledSearchbar = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | float: left;
7 | width: 14rem;
8 | margin-left: 6rem;
9 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
10 | ${(props) => {
11 | if (props.ishome) {
12 | return "display: none;";
13 | } else {
14 | return "width: 100%;margin: 0;";
15 | }
16 | }}
17 | }
18 | `;
19 |
20 | export const StyledSearchInput = styled.input`
21 | background: ${({ theme }) =>
22 | theme.name === "light" ? theme.colors.gray1 : theme.colors.gray7};
23 | border: 1px
24 | ${({ theme }) =>
25 | theme.name === "light" ? theme.colors.gray3 : theme.colors.gray7}
26 | solid;
27 | border-radius: 3px;
28 | color: ${({ theme }) => (theme.name === "light" ? "gray" : "black")};
29 | font-size: 1rem;
30 | height: 2rem;
31 | outline: none;
32 | padding: 0 2rem;
33 | width: 100%;
34 | &::placeholder {
35 | color: ${({ theme }) =>
36 | theme.name === "light" ? theme.colors.gray3 : theme.colors.gray2};
37 | font-size: 1rem;
38 | text-align: center;
39 | }
40 | &:focus::placeholder {
41 | text-align: left;
42 | }
43 | &:focus + .icon {
44 | right: 12.5rem;
45 | }
46 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
47 | margin: 0;
48 | padding: 0 0.6rem;
49 | }
50 | `;
51 |
52 | export const StyledSearchIcon = styled.span`
53 | align-self: center;
54 | font-size: .9rem;
55 | position: relative;
56 | color: ${({ theme }) =>
57 | theme.name === "light" ? theme.colors.gray3 : theme.colors.gray2};
58 | right: 9.1rem;
59 | ${(props) => {
60 | if (props.type === "fix") {
61 | return "right:89%;";
62 | } else if (props.type === "pseudo") {
63 | return "right: 67%;";
64 | }
65 | }}
66 | @media(max-width: ${({ theme }) => theme.breakpoint}){
67 | display: none;
68 | }
69 | transition: all .1s ease;
70 | `;
71 |
72 | export const StyledSearchResults = styled.div`
73 | display: flex;
74 | background: ${({ theme }) => theme.bg};
75 | border-bottom: 1px ${({ theme }) => theme.colors.gray1} solid;
76 | border-radius: 2px;
77 | flex-direction: column;
78 | margin: 3.5rem 0 0 -2rem;
79 | min-height: 0;
80 | max-height: 24rem;
81 | overflow-y: scroll;
82 | overflow-x: hidden;
83 | position: absolute;
84 | width: 19rem;
85 | left: 40%;
86 | border: 1px ${({ theme }) => theme.themeborder} solid;
87 | word-wrap: break-word;
88 | z-index: 6;
89 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
90 | left: 0;
91 | margin: 0;
92 | margin-top: 2.2rem;
93 | width: 100vw;
94 | }
95 | `;
96 |
97 | export const StyledClearBtn = styled.span`
98 | border: none;
99 | color: ${({ theme }) =>
100 | (theme.name === "light" && theme.colors.gray4) || "black"};
101 | cursor: pointer;
102 | font-size: 1.5rem;
103 | right: 44.5%;
104 | margin-bottom: 0.3rem;
105 | position: absolute;
106 | transform: rotate(45deg);
107 | transition: all 0.1s ease;
108 | z-index: 6;
109 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
110 | top: 0;
111 | right: 2%;
112 | }
113 | `;
114 |
115 | export const StyledAddBtn = styled.button`
116 | position: relative;
117 | background: none;
118 | border: none;
119 | border-radius: 50%;
120 | color: gray;
121 | margin-top: 3rem;
122 | cursor: pointer;
123 | outline: none;
124 | align-self: center;
125 | ${(props) => {
126 | switch (props.size) {
127 | case "sm":
128 | return "height: 2px;";
129 | case "mid":
130 | return "font-size: 2.4rem;margin: 1rem .4rem 1.3rem 1.4rem;";
131 | case "big":
132 | return "font-size: 3.2rem;margin: 1rem .4rem 1.3rem 1.4rem;";
133 | }
134 | }}
135 | &:hover {
136 | color: #0095f6;
137 | transition: color 0.2s ease;
138 | }
139 | @media (max-width: ${({ theme }) => theme.breakpoint}) {
140 | display: none;
141 | }
142 | `;
143 |
144 | export const StyledSearchedSub = styled.div`
145 | flex-direction: row;
146 | overflow-wrap: break-word;
147 | padding: 0.6rem;
148 | & > img {
149 | border: none;
150 | border-radius: 50%;
151 | float: left;
152 | height: 2.5rem;
153 | width: 2.5rem;
154 | }
155 | & > b {
156 | margin: 1rem;
157 | }
158 | & > p {
159 | margin: 0;
160 | margin-left: 3.3rem;
161 | padding: 0;
162 | }
163 | `;
164 |
165 | export const StyledSearchedSubContainer = styled.div`
166 | display: flex;
167 | flex-direction: row;
168 | border: none;
169 | border-bottom: 1px ${({ theme }) => theme.colors.gray2} solid;
170 | min-height: 4.8rem;
171 | align-self: center;
172 | overflow-y: hidden;
173 | width: 100%;
174 | justify-content: space-between;
175 | margin-left: 0.1rem;
176 | &:hover {
177 | background: ${({ theme }) => theme.colors.gray1};
178 | cursor: pointer;
179 | transition: background 0.2s ease;
180 | }
181 | `;
182 |
183 | export const StyledSearchedResultsArrow = styled.div`
184 | &::before {
185 | position: absolute;
186 | bottom: -14.5px;
187 | left: 48%;
188 | width: 0;
189 | height: 0;
190 | margin-top: -10px;
191 | border-top: 15px solid transparent;
192 | border-left: 15px solid transparent;
193 | border-right: 15px solid transparent;
194 | border-bottom: 15px solid ${({ theme }) => theme.bg};
195 | content: "";
196 | z-index: 7;
197 | }
198 | `;
199 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/components/Profile.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import Header from "./Header";
4 | import { getobj } from "../actions";
5 | import Menubar from "./Menubar";
6 | import PostCard from "./PostCard";
7 | import ViewPost from "./ViewPost";
8 | import FollowingTab from "./FollowingTab";
9 | import Trophies from "./Trophies";
10 | import Settings from "./Settings";
11 | import debounce from "lodash.debounce";
12 | import { addSubreddit, removeSubreddit, logout } from "../actions";
13 | import {
14 | StyledProfile,
15 | StyledProfileInfo,
16 | StyledProfileName,
17 | StyledProfileDetails,
18 | StyledProfilePosts,
19 | StyledProfileIcon,
20 | StyledButton,
21 | StyledInfoBlock,
22 | StyledProfileGallery,
23 | StyledInlineDiv,
24 | StyledLoginBoard,
25 | StyledButtonGroup,
26 | StyledLoader,
27 | } from "../styles/components/profileStyles";
28 | import { StyledBtnImg } from "../styles/components/headerStyles";
29 |
30 | class Profile extends React.Component {
31 | constructor(props) {
32 | super(props);
33 | this.state = {
34 | data: {},
35 | after: undefined,
36 | loading: false,
37 | currPost: {},
38 | viewPost: false,
39 | datafetched: false,
40 | followingTab: false,
41 | settings: false,
42 | trophies: false,
43 | };
44 | window.onscroll = debounce(() => {
45 | if (
46 | window.innerHeight + document.documentElement.scrollTop ===
47 | document.documentElement.scrollHeight + 12
48 | ) {
49 | this.setState({ loading: true });
50 | }
51 | }, 100);
52 | }
53 |
54 | viewPost = (data) => {
55 | this.setState({ viewPost: true, currPost: data });
56 | };
57 | hidepost = () => {
58 | this.setState({ viewPost: false, currPost: {} });
59 | };
60 | closeTab = (entity) => {
61 | this.setState({ [entity]: false });
62 | };
63 | trophies = () => {
64 | this.setState((prevState) => ({ trophies: !prevState.trophies }));
65 | };
66 | settings = () => {
67 | this.setState((prevState) => ({ settings: !prevState.settings }));
68 | };
69 | render() {
70 | let { authenticated, user } = this.props.authdata;
71 | return (
72 |
73 |
74 | {this.state.viewPost && (
75 |
79 | )}
80 | {this.state.followingTab && (
81 |
86 | )}
87 | {this.state.trophies && (
88 |
92 | )}
93 | {this.state.settings &&
}
94 |
95 |
96 |
97 |
98 |
99 | {`u/${user.name}`}
100 |
101 | this.trophies()}
108 | alt="trophies"
109 | type="trophies"
110 | />
111 | this.settings()}
118 | alt="settings"
119 | type="settings"
120 | />
121 |
122 |
123 |
124 | {user.fname}
125 | {authenticated && (
126 |
127 |
133 |
134 | {
135 | this.props.authdata.user
136 | .total_karma
137 | }
138 |
139 |
140 | )}
141 |
144 | this.setState({ followingTab: true })
145 | }
146 | >
147 | {this.props.subreddits.length} {" "}
148 | followings
149 |
150 |
151 |
152 |
153 |
154 |
155 | {(authenticated && (
156 |
159 | this.props.dispatch(logout())
160 | }
161 | >
162 | logout
163 |
164 | )) || (
165 |
166 | You are not logged in
167 |
171 |
172 | Login
173 |
174 |
175 |
176 | )}
177 |
178 |
179 | {Object.entries(this.state.data).map(
180 | ([key, value], index) => {
181 | return (
182 |
188 | );
189 | }
190 | )}
191 |
192 |
193 |
194 |
195 | {this.state.loading && (
196 |
201 | )}
202 |
203 |
204 |
205 | );
206 | }
207 | }
208 |
209 | const mapStateToProps = (state, props) => {
210 | return {
211 | subreddits: state.subredditReducer.subreddits,
212 | subdata: state.postReducer.data,
213 | authdata: state.authenticationReducer,
214 | access_token: state.authenticationReducer.access_token,
215 | nightmode: state.settingsReducer.nightmode,
216 | };
217 | };
218 |
219 | export default connect(mapStateToProps)(Profile);
220 |
--------------------------------------------------------------------------------
/src/components/SubredditProfile.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import Header from "./Header";
4 | import { getobj } from "../actions";
5 | import Menubar from "./Menubar";
6 | import PostCard from "./PostCard";
7 | import ViewPost from "./ViewPost";
8 | import FollowingTab from "./FollowingTab";
9 | import debounce from "lodash.debounce";
10 | import { addSubreddit, removeSubreddit } from "../actions";
11 | import { sub, unsub } from "../actions/api-calls";
12 |
13 | import {
14 | StyledProfile,
15 | StyledProfileInfo,
16 | StyledProfileName,
17 | StyledProfileDetails,
18 | StyledProfilePosts,
19 | StyledProfileIcon,
20 | StyledProfileGallery,
21 | StyledButton,
22 | StyledLoader,
23 | } from "../styles/components/profileStyles";
24 |
25 | class SubredditProfile extends React.Component {
26 | constructor(props) {
27 | super(props);
28 | this.state = {
29 | subdetails: {},
30 | posts: {},
31 | after: undefined,
32 | loading: false,
33 | currPostId: undefined,
34 | viewPost: false,
35 | subscriberTab: false,
36 | };
37 | window.onscroll = debounce(() => {
38 | if (
39 | window.innerHeight + document.documentElement.scrollTop ===
40 | document.documentElement.scrollHeight
41 | ) {
42 | this.setState({ loading: true });
43 | this.loadposts();
44 | }
45 | }, 100);
46 | this.loadContext = this.loadContext.bind(this);
47 | this.viewPost = this.viewPost.bind(this);
48 | this.hidePost = this.hidePost.bind(this);
49 | this.closeftab = this.closeftab.bind(this);
50 | this.unlisten = this.props.history.listen((location, action) => {
51 | this.setState({ posts: {}, after: undefined });
52 | this.loadContext(location.pathname);
53 | this.loadposts(location.pathname);
54 | this.forceUpdate();
55 | });
56 | }
57 | getObj = (data) => {
58 | const {
59 | id,
60 | title,
61 | created,
62 | description,
63 | name: full_name,
64 | display_name: name,
65 | header_img,
66 | icon_img,
67 | public_description,
68 | subscribers,
69 | banner_background_color,
70 | } = data;
71 | const obj = {
72 | id,
73 | title,
74 | created,
75 | description,
76 | name,
77 | full_name,
78 | header_img,
79 | icon_img,
80 | public_description,
81 | subscribers,
82 | banner_background_color,
83 | };
84 | return obj;
85 | };
86 |
87 | viewPost(id) {
88 | this.setState({ viewPost: true, currPostId: id });
89 | }
90 | hidePost() {
91 | this.setState({ viewPost: false, currPost: {} });
92 | }
93 | closeftab() {
94 | this.setState({ followingTab: false });
95 | }
96 |
97 | loadContext(location = `r/${this.props.match.params.subreddit}`) {
98 | fetch(`https://www.reddit.com/${location}/about/.json`)
99 | .then((res) => res.json())
100 | .then((json) => this.getObj(json.data))
101 | .then((data) => this.setState({ subdetails: data }))
102 | .catch((err) => {
103 | return;
104 | });
105 | }
106 | loadposts(subreddit = this.props.match.params.subreddit) {
107 | if (this.props.authenticated) {
108 | const token = this.props.access_token;
109 | var fetchedPosts = {};
110 | fetch(`https://oauth.reddit.com/r/${subreddit}/new`, {
111 | method: "GET",
112 | headers: {
113 | Authorization: `bearer ${token}`,
114 | "Content-Type": "application/x-www-form-urlencoded",
115 | },
116 | })
117 | .then((res) => res.json())
118 | .then((json) => {
119 | this.setState({ after: json.data.after });
120 | json.data.children.map((child) => {
121 | Object.assign(fetchedPosts, getobj(child.data));
122 | });
123 | })
124 | .then((posts) => this.setState({ posts: fetchedPosts }))
125 | .catch((err) =>
126 | console.log(
127 | "error while fetching posts from ",
128 | subreddit,
129 | err
130 | )
131 | );
132 | } else {
133 | fetch(
134 | `https://www.reddit.com/r/${this.props.match.params.subreddit}/.json?limit=7&after=${this.state.after}`
135 | )
136 | .then((res) => res.json())
137 | .then((json) => json.data)
138 | .then((data) => {
139 | data.children.map((post) => {
140 | const posts = {
141 | ...this.state.posts,
142 | ...getobj(post.data),
143 | };
144 | this.setState({ posts, after: data.after });
145 | });
146 | })
147 | .then((wait) => this.setState({ loading: false }))
148 | .catch((err) => {
149 | console.log(err);
150 | this.setState({ loading: false });
151 | });
152 | }
153 | }
154 | unsubHandler = () => {
155 | this.props.dispatch(removeSubreddit(this.props.match.params.subreddit));
156 | if (this.props.authenticated) {
157 | unsub(this.props.access_token, this.state.subdetails.full_name);
158 | }
159 | };
160 | subHandler = () => {
161 | this.props.dispatch(addSubreddit(this.props.match.params.subreddit));
162 | if (this.props.authenticated) {
163 | sub(this.props.access_token, this.state.subdetails.full_name);
164 | }
165 | };
166 | handleChanges = (id, score, likes) => {
167 | this.setState((prevState) => ({
168 | posts: {
169 | ...prevState.posts,
170 | [id]: {
171 | ...prevState.posts[id],
172 | score,
173 | likes,
174 | },
175 | },
176 | }));
177 | };
178 | componentDidMount() {
179 | this.loadContext();
180 | this.loadposts();
181 | }
182 |
183 | componentWillUnmount() {
184 | this.unlisten();
185 | }
186 | render() {
187 | let subreddit = this.props.match.params.subreddit;
188 | let {
189 | id,
190 | title,
191 | created,
192 | description,
193 | name,
194 | header_img,
195 | icon_img,
196 | public_description,
197 | subscribers,
198 | banner_background_color,
199 | } = this.state.subdetails;
200 | if (icon_img === "") {
201 | icon_img = "/images/icon.png";
202 | }
203 | return (
204 |
205 |
206 | {this.state.viewPost && (
207 |
212 | )}
213 | {this.state.followingTab && (
214 |
219 | )}
220 |
221 |
222 |
223 |
224 |
225 | {`r/${subreddit}`}
226 | {this.props.subreddits.includes(subreddit) ? (
227 | this.unsubHandler()}
230 | >
231 | unfollow
232 |
233 | ) : (
234 | this.subHandler()}
237 | >
238 | Follow
239 |
240 | )}
241 |
242 |
243 |
244 | {subscribers} followers
245 |
246 |
247 | {title}
248 |
249 | {public_description}
250 |
251 |
252 |
253 |
254 |
255 | {Object.entries(this.state.posts).map(
256 | ([key, value], index) => {
257 | return (
258 |
264 | );
265 | }
266 | )}
267 |
268 |
269 |
270 |
271 | {this.state.loading && (
272 |
277 | )}
278 |
279 |
280 |
281 | );
282 | }
283 | }
284 |
285 | const mapStateToProps = (state, props) => {
286 | return {
287 | subreddits: state.subredditReducer.subreddits,
288 | authenticated: state.authenticationReducer.authenticated,
289 | access_token: state.authenticationReducer.access_token,
290 | };
291 | };
292 |
293 | export default connect(mapStateToProps)(SubredditProfile);
294 |
--------------------------------------------------------------------------------
/src/components/LoadPost.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { NavLink } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import OptTab from "./OptTab";
5 | import Comments from "./Comments";
6 | import { upvote, downvote } from "../actions";
7 | import { editText, deleteCall } from "../actions/api-calls";
8 | import { StyledBtnImg } from "../styles/components/headerStyles";
9 | import {
10 | StyledCommentBox,
11 | StyledCommentInput,
12 | StyledPostButton,
13 | } from "../styles/components/commentStyles";
14 | import {
15 | StyledPostContainer,
16 | StyledPostTop,
17 | StyledPostMid,
18 | StyledInlineButtons,
19 | StyledPostBottom,
20 | } from "../styles/components/postStyles";
21 |
22 | class LoadPost extends Component {
23 | constructor(props) {
24 | super(props);
25 | this.state = {
26 | expanded: false,
27 | comments: [],
28 | count: 0,
29 | data: {},
30 | id_in_scope: undefined,
31 | index_in_scope: undefined,
32 | showopt: false,
33 | editmode: false,
34 | value: "",
35 | };
36 | this.commentRef = React.createRef();
37 | }
38 | readMore = (e) =>
39 | this.setState({
40 | expanded: true,
41 | });
42 | loadComments = (cmnt = "") => {
43 | fetch(
44 | `https://www.reddit.com/r/${this.props.data.subreddit}/comments/${this.props.data.id}.json`
45 | )
46 | .then((res) => res.json())
47 | .then((json) =>
48 | json[1].data.children.map((data) => {
49 | const {
50 | author,
51 | body: comment,
52 | created,
53 | name,
54 | depth,
55 | id,
56 | replies,
57 | ups,
58 | } = data.data;
59 | let obj = {
60 | author,
61 | comment,
62 | created,
63 | depth,
64 | name,
65 | id,
66 | replies,
67 | ups,
68 | };
69 | return obj;
70 | })
71 | )
72 | .then((comments) => {
73 | let index = comments.length;
74 | Object.assign(comments, { [index]: cmnt }, comments);
75 | this.setState({ comments, count: this.state.count + 10 });
76 | });
77 | };
78 | loadMore = () => this.setState({ count: this.state.count + 10 });
79 | hideComments = () => this.setState({ count: 0 });
80 | postComment = () => {
81 | fetch(`https://oauth.reddit.com/api/comment`, {
82 | method: "POST",
83 | headers: {
84 | Authorization: `bearer ${this.props.auth.access_token}`,
85 | "Content-Type": "application/x-www-form-urlencoded",
86 | },
87 | body: `api_type=json&return_rtjson=true&thing_id=${this.props.data.name}&text=${this.state.value}`,
88 | })
89 | .then((res) => res.json())
90 | .then((json) => {
91 | this.setState({ value: "" });
92 | const {
93 | author,
94 | body: comment,
95 | created,
96 | depth,
97 | id,
98 | replies,
99 | ups,
100 | } = json;
101 | let obj = {
102 | author,
103 | comment,
104 | created,
105 | depth,
106 | id,
107 | replies,
108 | ups,
109 | };
110 | this.loadComments(obj);
111 | })
112 | .catch((err) => {
113 | console.log("something went wrong", err);
114 | });
115 | };
116 | editComment = () => {
117 | let comments = this.state.comments;
118 | let comment = comments.splice(this.state.index_in_scope, 1);
119 | this.setState((prevState) => ({
120 | showopt: !prevState.showopt,
121 | editmode: true,
122 | value: comment[0].comment,
123 | comments,
124 | }));
125 | this.commentRef.current.focus();
126 | };
127 | submitEdited = () => {
128 | editText(
129 | this.props.auth.access_token,
130 | this.state.id_in_scope,
131 | this.state.value
132 | );
133 | this.setState({
134 | value: "",
135 | id_in_scope: undefined,
136 | index_in_scope: undefined,
137 | editmode: false,
138 | expanded: false,
139 | count: 0,
140 | });
141 | };
142 | CommentChangeHandler = (e) => {
143 | this.setState({ value: e.target.value });
144 | };
145 | _handleKeyDown = (e) => {
146 | if (e.key === "Enter") {
147 | if (this.state.editmode) this.submitEdited();
148 | else this.postComment();
149 | }
150 | };
151 | del_comment = () => {
152 | let comments = this.state.comments;
153 | comments.splice(this.state.index_in_scope, 1);
154 | deleteCall(this.props.auth.access_token, this.state.id_in_scope);
155 | this.setState({ comments, id_in_scope: false, index_in_scope: false });
156 | };
157 | toggleOpt = (e, full_name = undefined, index = undefined) => {
158 | if (e.target.getAttribute("type") === "optcanvas") {
159 | this.setState((prevState) => ({
160 | showopt: !prevState.showopt,
161 | id_in_scope: full_name,
162 | index_in_scope: index,
163 | }));
164 | }
165 | };
166 | render() {
167 | const ishome = this.props.ishome;
168 | const token = this.props.auth.access_token;
169 | let {
170 | id,
171 | name,
172 | url,
173 | post,
174 | caption,
175 | username,
176 | subreddit,
177 | ups,
178 | likes,
179 | score,
180 | is_video,
181 | } = this.props.data;
182 | const ext = url.substring(url.length - 4, url.length);
183 | let img =
184 | ext === ".jpg" ||
185 | ext === ".png" ||
186 | ext === ".gif" ||
187 | ext === "gifv";
188 | if (!img && url.includes("imgur")) {
189 | url += ".png";
190 | img = true;
191 | }
192 | is_video = url.includes("youtu");
193 | return (
194 |
195 | {this.state.showopt && (
196 |
201 | )}
202 |
203 | {ishome ? (
204 |
208 |
215 | r/{subreddit}
216 |
217 | ) : (
218 |
222 | u/{username}
223 |
224 | )}
225 |
226 |
227 | {is_video && (
228 |
236 | )}
237 | {img && (
238 |
242 | (e.target.src =
243 | "https://pics.me.me/couldnt-load-image-tap-to-retry-15674211.png")
244 | }
245 | />
246 | )}
247 | {!img && !is_video && (
248 |
249 | {post}
250 | {!post && {url} }
251 |
252 | )}
253 |
254 |
255 | {this.props.auth.authenticated && (
256 |
257 |
260 | this.props.dispatch(
261 | upvote(
262 | ishome,
263 | token,
264 | id,
265 | name,
266 | score,
267 | likes
268 | )
269 | )
270 | }
271 | src={
272 | likes
273 | ? "/images/upvote-active.png"
274 | : "/images/upvote.png"
275 | }
276 | />
277 |
280 | this.props.dispatch(
281 | downvote(
282 | ishome,
283 | token,
284 | id,
285 | name,
286 | score,
287 | likes
288 | )
289 | )
290 | }
291 | src={
292 | likes === false
293 | ? "/images/downvote-active.png"
294 | : "/images/upvote.png"
295 | }
296 | />
297 |
298 | )}
299 |
300 | {score} {(parseInt(score) > 1 && "upvotes") || "upvote"}
301 |
302 |
303 | {this.state.expanded || caption.length < 80 ? (
304 |
305 |
306 | u/{username}
307 |
308 | {caption}
309 |
310 | ) : (
311 |
312 |
313 | u/{username}
314 |
315 |
316 | {caption.substring(0, 80)}
317 | ... more
318 |
319 | )}
320 |
329 | {this.props.auth.authenticated && (
330 |
331 |
334 |
361 | )}
362 |
363 |
364 |
365 | );
366 | }
367 | }
368 |
369 | export default connect()(LoadPost);
370 |
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_SUBREDDIT,
3 | ADD_SUBREDDITS,
4 | SELECT_SUBREDDIT,
5 | LOGGED_IN,
6 | UPDATE_TOKENS,
7 | UPVOTE,
8 | DOWNVOTE,
9 | DESELECT_SUBREDDIT,
10 | NIGHTMODE,
11 | LOAD_HOMEPAGE,
12 | LOGOUT,
13 | HOMEPAGE,
14 | HOMEPAGEPOST_UPVOTE,
15 | HOMEPAGEPOST_DOWNVOTE,
16 | REQUEST_POSTS,
17 | RECEIVE_POSTS,
18 | REMOVE_SUBREDDIT,
19 | } from "./action-types";
20 | import store from "../store";
21 | import {
22 | getUserInfo,
23 | getSubInfo,
24 | getSubreddit,
25 | refreshData,
26 | } from "./api-calls";
27 |
28 | const request_posts = (subreddit) => ({
29 | type: REQUEST_POSTS,
30 | payload: { subreddit },
31 | });
32 |
33 | const receive_posts = (subreddit, items, posts, after) => ({
34 | type: RECEIVE_POSTS,
35 | payload: {
36 | subreddit,
37 | items,
38 | posts,
39 | after,
40 | lastUpdated: Date.now(),
41 | },
42 | });
43 |
44 | export const homepage = (fetchedPosts, after) => ({
45 | type: HOMEPAGE,
46 | payload: {
47 | fetchedPosts,
48 | after,
49 | lastUpdated: Date.now(),
50 | },
51 | });
52 |
53 | export const select_subreddit = (subreddit) => ({
54 | type: SELECT_SUBREDDIT,
55 | payload: { subreddit },
56 | });
57 |
58 | export const deselect_subreddit = () => ({
59 | type: DESELECT_SUBREDDIT,
60 | });
61 |
62 | const add_subreddit = (subreddit, icon, authenticated = false) => ({
63 | type: ADD_SUBREDDIT,
64 | payload: { subreddit, icon, authenticated },
65 | });
66 |
67 | const add_subreddits = (subs, subdata) => ({
68 | type: ADD_SUBREDDITS,
69 | payload: { subs, subdata },
70 | });
71 |
72 | export const remove_subreddit = (subreddit) => ({
73 | type: REMOVE_SUBREDDIT,
74 | payload: { subreddit },
75 | });
76 |
77 | export const logged_in = (data, userInfo) => ({
78 | type: LOGGED_IN,
79 | payload: { data, userInfo },
80 | });
81 |
82 | export const getobj = (data) => {
83 | const {
84 | id,
85 | name,
86 | created,
87 | title: caption,
88 | selftext: post,
89 | author: username,
90 | downs,
91 | score,
92 | num_comments,
93 | subreddit,
94 | ups,
95 | likes,
96 | url,
97 | is_video,
98 | } = data;
99 | let obj = {};
100 | obj[id] = {
101 | id,
102 | name,
103 | created,
104 | caption,
105 | post,
106 | username,
107 | subreddit,
108 | num_comments,
109 | ups,
110 | downs,
111 | likes,
112 | score,
113 | url,
114 | is_video,
115 | hovering: false,
116 | };
117 | return obj;
118 | };
119 |
120 | export const fetchPosts = (subreddit) => {
121 | return async (dispatch, getState) => {
122 | const token = getState().authenticationReducer.access_token;
123 | const data = getState().postReducer.data;
124 | let after = undefined;
125 | if (data.hasOwnProperty(subreddit)) {
126 | after = getState().postReducer.data[subreddit].after;
127 | }
128 | dispatch(request_posts(subreddit));
129 | if (getState().authenticationReducer.authenticated) {
130 | return await fetch(
131 | `https://oauth.reddit.com/r/${subreddit}/new?after=${after}`,
132 | {
133 | method: "GET",
134 | headers: {
135 | Authorization: `bearer ${token}`,
136 | "Content-Type": "application/x-www-form-urlencoded",
137 | },
138 | }
139 | )
140 | .then((res) => res.json())
141 | .then((json) => {
142 | after = json.data.after;
143 | let posts = {};
144 | let items = [];
145 | json.data.children.map((child) => {
146 | items.push(child.data.id);
147 | posts = { ...posts, ...getobj(child.data) };
148 | });
149 | return { items, posts, after };
150 | })
151 | .then(({ items, posts, after }) => {
152 | dispatch(receive_posts(subreddit, items, posts, after));
153 | })
154 | .catch((err) =>
155 | console.log("error while fetching posts from ", subreddit)
156 | );
157 | } else {
158 | return fetch(
159 | `https://www.reddit.com/r/${subreddit}.json?after=${after}`
160 | )
161 | .then((resp) => resp.json())
162 | .then((json) => {
163 | after = json.data.after;
164 | let items = [];
165 | let posts = {};
166 | json.data.children.map((child) => {
167 | items.push(child.data.id);
168 | posts = { ...posts, ...getobj(child.data) };
169 | });
170 | return { items, posts, after };
171 | })
172 | .then(({ items, posts, after }) =>
173 | dispatch(receive_posts(subreddit, items, posts, after))
174 | )
175 | .catch((err) =>
176 | console.log("error while fetching posts from ", subreddit)
177 | );
178 | }
179 | };
180 | };
181 |
182 | export const getHomePage = () => {
183 | return async (dispatch, getState) => {
184 | dispatch({ type: LOAD_HOMEPAGE });
185 | const token = getState().authenticationReducer.access_token;
186 | const after = getState().postReducer.homepage.after;
187 | var posts = {};
188 | if (getState().authenticationReducer.authenticated) {
189 | fetch(`https://oauth.reddit.com/new?after=${after}`, {
190 | method: "GET",
191 | headers: {
192 | Authorization: `bearer ${token}`,
193 | "Content-Type": "application/x-www-form-urlencoded",
194 | },
195 | })
196 | .then((res) => res.json())
197 | .then((json) => {
198 | const after = json.data.after;
199 | json.data.children.map((child) => {
200 | let post = getobj(child.data);
201 | posts = { ...posts, ...post };
202 | });
203 | return { posts, after };
204 | })
205 | .then(({ posts, after }) => dispatch(homepage(posts, after)))
206 | .catch((err) => console.log("error while fetching homepage"));
207 | } else {
208 | fetch(`https://www.reddit.com/.json?after=${after}`)
209 | .then((resp) => resp.json())
210 | .then((json) => {
211 | const after = json.data.after;
212 | json.data.children.map((child) => {
213 | let post = getobj(child.data);
214 | posts = { ...posts, ...post };
215 | });
216 | return { posts, after };
217 | })
218 | .then(({ posts, after }) => dispatch(homepage(posts, after)))
219 | .catch((err) =>
220 | console.log("error while fetching homepage", err)
221 | );
222 | }
223 | };
224 | };
225 |
226 | export const addSubreddit = (subreddit) => {
227 | return (dispatch, getState) => {
228 | return fetch(`https://www.reddit.com/r/${subreddit}/about.json`)
229 | .then((resp) => resp.json())
230 | .then((json) => json.data)
231 | .then((data) => {
232 | let { icon_img } = data;
233 | if (!icon_img.length)
234 | icon_img = "https://i.redd.it/130am13nj6201.png";
235 | dispatch(
236 | add_subreddit(
237 | subreddit,
238 | icon_img,
239 | getState().authenticationReducer.authenticated
240 | )
241 | );
242 | })
243 | .catch((err) => console.log("error while adding subreddit", err));
244 | };
245 | };
246 |
247 | export const addSubreddits = (subInfo, authenticated = false) => {
248 | let subdata = {};
249 | let subs = [];
250 | subInfo.subs.map((sub) => {
251 | subs.push(sub.name);
252 | subdata[sub.name] = {
253 | fetching: false,
254 | items: [],
255 | icon: sub.icon,
256 | lastUpdate: Date.now(),
257 | };
258 | });
259 | return (dispatch, getState) => {
260 | dispatch(add_subreddits(subs, subdata));
261 | };
262 | };
263 |
264 | export const removeSubreddit = (subreddit) => {
265 | return (dispatch, getState) => {
266 | dispatch(remove_subreddit(subreddit));
267 | };
268 | };
269 |
270 | export const loggedIn = (data) => {
271 | return async (dispatch, getState) => {
272 | const token = data.access_token;
273 | const userInfo = await getUserInfo(token);
274 | dispatch(logged_in(data, userInfo));
275 | dispatch(getHomePage());
276 | dispatch(nightMode(userInfo.pref_nightmode));
277 | setTimeout(() => {
278 | getRefreshToken(data.access_token, data.refresh_token);
279 | }, 3540000);
280 | window.localStorage.setItem("data", JSON.stringify(data));
281 | window.localStorage.setItem("nightMode", userInfo.pref_nightmode);
282 | const subInfo = await getSubInfo(token);
283 | dispatch(addSubreddits(subInfo));
284 | };
285 | };
286 |
287 | export const logout = () => {
288 | window.localStorage.removeItem("data");
289 | window.localStorage.removeItem("nightMode");
290 | return (dispatch, getState) => {
291 | dispatch({ type: LOGOUT });
292 | };
293 | };
294 |
295 | export const upvote = (ishome = false, token, id, name, score, likes) => {
296 | if (likes) {
297 | var dir = 0;
298 | var likes = null;
299 | var score = score - 1;
300 | } else if (likes === false) {
301 | var dir = 0;
302 | var likes = true;
303 | var score = score + 2;
304 | } else {
305 | var dir = 1;
306 | var likes = true;
307 | var score = score + 1;
308 | }
309 | fetch("https://oauth.reddit.com/api/vote", {
310 | method: "POST",
311 | headers: {
312 | Authorization: `bearer ${token}`,
313 | "Content-Type": "application/x-www-form-urlencoded",
314 | },
315 | body: `dir=${dir}&id=${name}&rank=3`,
316 | }).catch((err) => console.log("error occured while upvoting: ", err));
317 | if (ishome) {
318 | return (dispatch, getState) => {
319 | dispatch({
320 | type: HOMEPAGEPOST_UPVOTE,
321 | payload: { id, likes, score },
322 | });
323 | };
324 | }
325 | return (dispatch, getState) => {
326 | dispatch({ type: UPVOTE, payload: { id, likes, score } });
327 | };
328 | };
329 |
330 | export const downvote = (ishome, token, id, name, score, likes) => {
331 | if (likes) {
332 | var dir = -1;
333 | var likes = false;
334 | var score = score - 2;
335 | } else if (likes === false) {
336 | var dir = 0;
337 | var likes = null;
338 | var score = score + 1;
339 | } else {
340 | var dir = -1;
341 | var likes = false;
342 | var score = score - 1;
343 | }
344 | fetch("https://oauth.reddit.com/api/vote", {
345 | method: "POST",
346 | headers: {
347 | Authorization: `bearer ${token}`,
348 | "Content-Type": "application/x-www-form-urlencoded",
349 | },
350 | body: `dir=${dir}&id=${name}&rank=3`,
351 | }).catch((err) => console.log("error occured while downvoting: ", err));
352 | if (ishome) {
353 | return (dispatch, getState) => {
354 | dispatch({
355 | type: HOMEPAGEPOST_DOWNVOTE,
356 | payload: { id, likes, score },
357 | });
358 | };
359 | }
360 | return (dispatch, getState) => {
361 | dispatch({ type: DOWNVOTE, payload: { id, likes, score } });
362 | };
363 | };
364 |
365 | export const nightMode = (toggleState = "not_specified") => {
366 | if (toggleState === "not_specified") {
367 | return (dispatch, getState) => {
368 | dispatch({
369 | type: NIGHTMODE,
370 | payload: { nightmode: !getState().settingsReducer.nightmode },
371 | });
372 | };
373 | } else {
374 | return (dispatch, getState) => {
375 | dispatch({ type: NIGHTMODE, payload: { nightmode: toggleState } });
376 | };
377 | }
378 | };
379 |
380 | export const getRefreshToken = async (access_token, refresh_token) => {
381 | refreshData(access_token, refresh_token).then(async (data) => {
382 | store.dispatch({ type: UPDATE_TOKENS, payload: data });
383 | await new Promise((r) => setTimeout(r, 3540000));
384 | if (store.getState().authenticationReducer.authenticated)
385 | await getRefreshToken(data.access_token, refresh_token);
386 | });
387 | };
388 |
--------------------------------------------------------------------------------
/src/components/ViewPost.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { NavLink, withRouter } from "react-router-dom";
4 | import Comments from "./Comments";
5 | import OptTab from "./OptTab";
6 | import { upvote, downvote } from "../actions";
7 | import { editText, deleteCall } from "../actions/api-calls";
8 | import store from "../store";
9 | import { addSubreddit, removeSubreddit } from "../actions";
10 | import { StyledBtnImg } from "../styles/components/headerStyles";
11 | import {
12 | StyledViewPostContent,
13 | StyledViewPostDetails,
14 | StyledCommentSection,
15 | StyledDetailsFooter,
16 | StyledDetailSection,
17 | StyledVPTextPost,
18 | StyledVPCloseBtn,
19 | StyledPostHead,
20 | StyledFollowBtn,
21 | } from "../styles/components/viewPostStyles";
22 | import { StyledCanvas } from "../styles/components/tabStyles";
23 | import {
24 | StyledCommentBox,
25 | StyledCommentInput,
26 | StyledPostButton,
27 | } from "../styles/components/commentStyles";
28 |
29 | class ViewPost extends React.Component {
30 | constructor(props) {
31 | super(props);
32 | this.state = {
33 | count: 0,
34 | comments: [],
35 | id_in_scope: undefined,
36 | index_in_scope: undefined,
37 | showopt: false,
38 | value: "",
39 | };
40 | this.commentRef = React.createRef();
41 | this.loadComments = this.loadComments.bind(this);
42 | this.loadMore = this.loadMore.bind(this);
43 | this.hideComments = this.hideComments.bind(this);
44 | }
45 | editComment = () => {
46 | let comments = this.state.comments;
47 | let comment = comments.splice(this.state.index_in_scope, 1);
48 | this.setState((prevState) => ({
49 | showopt: !prevState.showopt,
50 | editmode: true,
51 | value: comment[0].comment,
52 | comments,
53 | }));
54 | this.commentRef.current.focus();
55 | };
56 | submitEdited = () => {
57 | editText(
58 | this.props.auth.access_token,
59 | this.state.id_in_scope,
60 | this.state.value
61 | );
62 | this.setState({
63 | value: "",
64 | id_in_scope: undefined,
65 | index_in_scope: undefined,
66 | editmode: false,
67 | expanded: false,
68 | count: 0,
69 | });
70 | };
71 | del_comment = () => {
72 | let comments = this.state.comments;
73 | comments.splice(this.state.index_in_scope, 1);
74 | deleteCall(this.props.auth.access_token, this.state.id_in_scope);
75 | this.setState({ comments, id_in_scope: false, index_in_scope: false });
76 | };
77 | loadComments = (cmnt = "") => {
78 | fetch(
79 | `https://www.reddit.com/r/${this.props.data.subreddit}/comments/${this.props.data.id}.json`
80 | )
81 | .then((res) => res.json())
82 | .then((json) =>
83 | json[1].data.children.map((data) => {
84 | const {
85 | author,
86 | body: comment,
87 | created,
88 | depth,
89 | name,
90 | id,
91 | replies,
92 | ups,
93 | } = data.data;
94 | let obj = {
95 | author,
96 | comment,
97 | created,
98 | depth,
99 | name,
100 | id,
101 | replies,
102 | ups,
103 | };
104 | return obj;
105 | })
106 | )
107 | .then((comments) => {
108 | let index = comments.length;
109 | Object.assign(comments, { [index]: cmnt }, comments);
110 | this.setState({ comments, count: this.state.count + 10 });
111 | });
112 | };
113 | handleUpvote = (token, id, name, score, likes) => {
114 | this.props.dispatch(upvote(token, id, name, score, likes));
115 | if (store.getState().postReducer.posts.hasOwnProperty(id)) {
116 | const { score, likes } = store.getState().postReducer.posts[id];
117 | this.props.handleChanges(id, score, likes);
118 | }
119 | };
120 | handleDownvote = (token, id, name, score, likes) => {
121 | this.props.dispatch(downvote(token, id, name, score, likes));
122 | if (store.getState().postReducer.posts.hasOwnProperty(id)) {
123 | const { score, likes } = store.getState().postReducer.posts[id];
124 | this.props.handleChanges(id, score, likes);
125 | }
126 | };
127 | loadMore = () => this.setState({ count: this.state.count + 10 });
128 | hideComments = () => this.setState({ count: 0 });
129 |
130 | handleCloseSettings = (e) => {
131 | if (e.target.getAttribute("type") === "canvas") {
132 | this.props.hidePost();
133 | }
134 | };
135 |
136 | toggleOpt = (e, full_name = undefined) => {
137 | if (e.target.getAttribute("type") === "optcanvas") {
138 | this.setState((prevState) => ({
139 | showopt: !prevState.showopt,
140 | id_in_scope: full_name,
141 | }));
142 | }
143 | };
144 | postComment = () => {
145 | fetch(`https://oauth.reddit.com/api/comment`, {
146 | method: "POST",
147 | headers: {
148 | Authorization: `bearer ${this.props.auth.access_token}`,
149 | "Content-Type": "application/x-www-form-urlencoded",
150 | },
151 | body: `api_type=json&return_rtjson=true&thing_id=${this.props.data.name}&text=${this.state.value}`,
152 | })
153 | .then((res) => res.json())
154 | .then((json) => {
155 | this.setState({ value: "" });
156 | const {
157 | author,
158 | body: comment,
159 | created,
160 | depth,
161 | name,
162 | id,
163 | replies,
164 | ups,
165 | } = json;
166 | let obj = {
167 | author,
168 | comment,
169 | created,
170 | name,
171 | depth,
172 | id,
173 | replies,
174 | ups,
175 | };
176 | this.loadComments(obj);
177 | })
178 | .catch((err) => {
179 | console.log("something went wrong", err);
180 | });
181 | };
182 | CommentChangeHandler = (e) => {
183 | this.setState({ value: e.target.value });
184 | };
185 | _handleKeyDown = (e) => {
186 | if (e.key === "Enter") {
187 | if (this.state.editmode) this.submitEdited();
188 | else this.postComment();
189 | }
190 | };
191 | render() {
192 | let token = this.props.auth.access_token;
193 | let {
194 | id,
195 | name,
196 | url,
197 | post,
198 | caption,
199 | username,
200 | subreddit,
201 | ups,
202 | likes,
203 | score,
204 | is_video,
205 | } = this.props.data;
206 | const ext = url.substring(url.length - 4, url.length);
207 | let img =
208 | ext === ".jpg" ||
209 | ext === ".png" ||
210 | ext === ".gif" ||
211 | ext === "gifv";
212 | if (!img && url.includes("imgur")) {
213 | url += ".png";
214 | img = true;
215 | }
216 | is_video = url.includes("youtu");
217 |
218 | return (
219 | this.handleCloseSettings(e)}
222 | >
223 | {this.state.showopt && (
224 |
229 | )}
230 |
231 | +
232 |
233 |
234 |
235 |
236 | {(this.props.location.pathname ===
237 | `/r/${this.props.data.subreddit}` && (
238 |
239 | r/{subreddit}
240 |
241 | )) || (
242 |
246 | r/{subreddit}
247 |
248 | )}
249 | {this.props.subreddits.includes(subreddit) ? (
250 |
253 | this.props.dispatch(
254 | removeSubreddit(subreddit)
255 | )
256 | }
257 | >
258 | unfollow
259 |
260 | ) : (
261 |
263 | this.props.dispatch(
264 | addSubreddit(subreddit)
265 | )
266 | }
267 | >
268 | Follow
269 |
270 | )}
271 |
272 |
273 | {img && }
274 | {is_video && (
275 |
284 | )}
285 | {!img && !is_video && (
286 |
287 | {caption}
288 | {post.substring(0, 400)}
289 |
290 | {!post && (
291 |
297 | {url}
298 |
299 | )}
300 |
301 |
302 | )}
303 |
304 |
305 |
306 | {(this.props.location.pathname ===
307 | `/r/${this.props.data.subreddit}` && (
308 | r/{subreddit}
309 | )) || (
310 |
314 | r/{subreddit}
315 |
316 | )}
317 | {this.props.subreddits.includes(subreddit) ? (
318 |
321 | this.props.dispatch(
322 | removeSubreddit(subreddit)
323 | )
324 | }
325 | >
326 | unfollow
327 |
328 | ) : (
329 |
331 | this.props.dispatch(addSubreddit(subreddit))
332 | }
333 | >
334 | Follow
335 |
336 | )}
337 |
338 |
339 | u/{username}
340 | {caption}
341 |
350 |
351 |
352 | {this.props.auth.authenticated && (
353 |
354 |
357 | this.handleUpvote(
358 | token,
359 | id,
360 | name,
361 | score,
362 | likes
363 | )
364 | }
365 | src={
366 | likes
367 | ? "/images/upvote-active.png"
368 | : "/images/upvote.png"
369 | }
370 | />
371 |
374 | this.handleDownvote(
375 | token,
376 | id,
377 | name,
378 | score,
379 | likes
380 | )
381 | }
382 | src={
383 | likes === false
384 | ? "/images/downvote-active.png"
385 | : "/images/upvote.png"
386 | }
387 | />
388 |
389 | )}
390 |
391 | {score}{" "}
392 | {(parseInt(score) > 1 && "upvotes") || "upvote"}
393 |
394 | {this.props.auth.authenticated && (
395 |
396 |
399 |
404 | this.CommentChangeHandler(e)
405 | }
406 | onKeyDown={this._handleKeyDown}
407 | placeholder="Add Comment"
408 | value={this.state.value}
409 | />
410 | {(!this.state.editmode && (
411 | this.postComment()}
414 | >
415 | POST
416 |
417 | )) || (
418 | this.submitEdited()}
421 | >
422 | EDIT
423 |
424 | )}
425 |
426 | )}
427 |
428 |
429 |
430 | );
431 | }
432 | }
433 |
434 | const mapStateToProps = (state) => {
435 | return {
436 | subreddits: state.subredditReducer.subreddits,
437 | auth: state.authenticationReducer,
438 | posts: state.postReducer.posts,
439 | };
440 | };
441 |
442 | export default withRouter(connect(mapStateToProps)(ViewPost));
443 |
--------------------------------------------------------------------------------