├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── demo
├── Feed.png
└── Profile.png
├── package-lock.json
├── package.json
├── public
├── cruzz_icon.ico
├── cruzz_launcher.png
├── cruzz_splash.png
├── cruzz_splash2.png
├── index.html
├── index.svg
├── manifest.json
└── uikit
│ ├── css
│ └── uikit.min.css
│ └── js
│ ├── uikit-icons.min.js
│ └── uikit.min.js
├── src
├── actions
│ ├── authActions.js
│ └── types.js
├── components
│ ├── App.js
│ ├── PrivateRoute.js
│ ├── common
│ │ ├── Followers.js
│ │ ├── Header.js
│ │ ├── PageSuggestions.js
│ │ ├── PostOperations.js
│ │ ├── SignIn.js
│ │ └── SignUp.js
│ ├── feed
│ │ ├── PostDetails.js
│ │ ├── PostFeed.js
│ │ └── PostsByTags.js
│ └── profile
│ │ ├── FavoritePosts.js
│ │ ├── ProfilePage.js
│ │ └── UserProfile.js
├── firebase
│ └── index.js
├── index.js
├── logo.svg
├── reducers
│ ├── authReducer.js
│ └── index.js
├── registerServiceWorker.js
├── static
│ ├── css
│ │ ├── common.css
│ │ └── style.css
│ └── img
│ │ ├── avtar.jpg
│ │ ├── index-no-spin.svg
│ │ ├── index.svg
│ │ ├── index2.svg
│ │ ├── index3.svg
│ │ ├── index4.svg
│ │ ├── no-feeds.png
│ │ └── retro-hop.jpg
├── store.js
├── tests
│ └── App.test.js
└── utils
│ ├── setAuthToken.js
│ └── validate.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | trim_trailing_whitespace = true
5 | end_of_line = lf
6 |
7 | [*.{js,css}]
8 | indent_style = space
9 | indent_size = 2
10 | charset = utf-8
11 | insert_final_newline = true
12 | max_line_length = 120
13 |
14 | [*.html]
15 | indent_style = space
16 | indent_size = 4
17 | charset = utf-8
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 | 4. See error
16 |
17 | **Expected behavior**
18 | A clear and concise description of what you expected to happen.
19 |
20 | **Screenshots**
21 | If applicable, add screenshots to help explain your problem.
22 |
23 | **Desktop (please complete the following information):**
24 | - OS: [e.g. iOS]
25 | - Browser [e.g. chrome, safari]
26 | - Version [e.g. 22]
27 |
28 | **Smartphone (please complete the following information):**
29 | - Device: [e.g. iPhone6]
30 | - OS: [e.g. iOS8.1]
31 | - Browser [e.g. stock browser, safari]
32 | - Version [e.g. 22]
33 |
34 | **Additional context**
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Fixes {ISSUE_LINK}
2 |
3 | Please provide a short description of what your change does.
4 |
5 | ## Related Issues
6 |
7 | - Link to issues fixed
8 |
9 | ## Screenshots for the change:
10 |
11 | - PC
12 |
13 | - Mobile
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/# See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | /node_modules
6 |
7 | # testing
8 | /coverage
9 |
10 | # production
11 | /build
12 |
13 | # misc
14 | .DS_Store
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
24 | .idea
25 |
26 | webpack-stats.json
27 | dist
28 |
29 | .firebase/
30 | .vscode/
31 | .idea/
32 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - '8'
5 |
6 | cache:
7 | yarn: true
8 | directories:
9 | - node_modules
10 |
11 | install:
12 | - yarn
13 |
14 | script:
15 | - yarn test
16 | - yarn build
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Mohit Kumar Yadav
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cruzz-web
2 |

3 |
4 |
5 |
6 |
7 |
8 |
9 | | Features | Status |
10 | | ------------- |:-------------:|
11 | | Sign Up/Login | :white_check_mark: |
12 | | Profile | :white_check_mark: |
13 | | Follow/Unfollow a user | :white_check_mark: |
14 | | Update user details and profile picture/cover | :white_check_mark: |
15 | | Post(with or without tags) | :white_check_mark: |
16 | | Search(with or without tags) | :white_check_mark: |
17 | | Allow html amd Emoji inputs in post | :white_check_mark: |
18 | | **Render raw html input from a post** | :white_check_mark: |
19 | | Edit/Delete a post | :white_check_mark: |
20 | | Upvote/Downvote a post | :white_check_mark: |
21 | | View/Add/Remove a post to favourites | :white_check_mark: |
22 | | Comment(edit/delete) on a post | :white_check_mark: |
23 | | Chat | :clock4: |
24 | | Anonymous polls | :clock4: |
25 |
26 |
27 | ## Usage instructions
28 | * signup with a valid email
29 | * Make sure https://cruzz.herokuapp.com is live and working
30 |
31 |
32 |
33 | ## Demo
34 | ### Feed Page
35 |
36 |
37 |
38 | ### Profile Page
39 |
40 |
41 |
42 | ## Useful commands
43 |
44 | ### run
45 | ```
46 | yarn start
47 | ```
48 | ```
49 | yard build
50 | ```
51 | ```
52 | yard test
53 | ```
--------------------------------------------------------------------------------
/demo/Feed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/demo/Feed.png
--------------------------------------------------------------------------------
/demo/Profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/demo/Profile.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cruzz",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "http://mohitkyadav.github.io/cruzz-web",
6 | "dependencies": {
7 | "axios": "^0.18.0",
8 | "firebase": "^5.5.8",
9 | "jwt-decode": "^2.2.0",
10 | "react": "^16.5.0",
11 | "react-dom": "^16.5.0",
12 | "react-redux": "^5.0.7",
13 | "react-router-dom": "^4.3.1",
14 | "react-scripts": "1.1.5",
15 | "redux": "^4.0.0",
16 | "redux-devtools-extension": "^2.13.5",
17 | "redux-thunk": "^2.3.0"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test --env=jsdom",
23 | "eject": "react-scripts eject",
24 | "predeploy": "npm run build",
25 | "deploy": "gh-pages -d build"
26 | },
27 | "devDependencies": {
28 | "gh-pages": "^1.2.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/public/cruzz_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/public/cruzz_icon.ico
--------------------------------------------------------------------------------
/public/cruzz_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/public/cruzz_launcher.png
--------------------------------------------------------------------------------
/public/cruzz_splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/public/cruzz_splash.png
--------------------------------------------------------------------------------
/public/cruzz_splash2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/public/cruzz_splash2.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Home
32 |
33 |
34 |
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/public/index.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "VCONNECT",
3 | "name": "Welcome aboard!",
4 | "icons": [
5 | {
6 | "src": "cruzz_icon.ico",
7 | "sizes": "64x64",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "cruzz_launcher.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "cruzz_splash2.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": "./index.html",
22 | "display": "standalone",
23 | "theme_color": "#23aeaa",
24 | "background_color": "#804fd7"
25 | }
26 |
--------------------------------------------------------------------------------
/src/actions/authActions.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | import { SET_CURRENT_USER, SET_CURRENT_PROFILE, LOADING, LOADED, AUTHENTICATED, ERROR } from './types';
4 |
5 | import setAuthToken from '../utils/setAuthToken';
6 | import { isEmpty } from './../utils/validate';
7 |
8 | export const updateUserProfile = user => dispatch => {
9 | dispatch(loading());
10 | axios.post(
11 | 'https://cruzz.herokuapp.com/api/authentication/users/update/',
12 | user
13 | ).then(res => {
14 | console.log(res.data);
15 | dispatch(setCurrentUser(res.data.user.username));
16 | dispatch(loaded());
17 | }).catch(err => {
18 | console.log(err.response);
19 | dispatch(loaded());
20 | });
21 | }
22 |
23 | export const signUp = (user, history) => dispatch => {
24 | dispatch(loading());
25 | axios.post(
26 | 'https://cruzz.herokuapp.com/api/authentication/users/registration',
27 | user
28 | ).then(res => {
29 | dispatch(loaded());
30 | history.push('/login');
31 | // Save / Set token to local storage
32 | // console.log(res.data);
33 | // const { token, username } = res.data.user;
34 | // localStorage.setItem('jwtToken', 'Token ' + token);
35 | // localStorage.setItem('username', username);
36 | // // Set auth header
37 | // setAuthToken('Token ' + token);
38 | // axios.get(`https://cruzz.herokuapp.com/api/profile/retrieve/${username}/`).then(response => {
39 | // console.log(response.data);
40 | // dispatch({ type: SET_CURRENT_USER, payload: response.data.user });
41 | // dispatch({ type: ERROR, payload: '' });
42 | // history.push('/');
43 | // }).catch(err => {
44 | // console.log(err.response);
45 | // dispatch(loaded());
46 | // });
47 | }).catch(err => {
48 | console.log(err.response);
49 | dispatch({ type: ERROR, payload: err.response.data.errors });
50 | dispatch(loaded());
51 | });
52 | };
53 |
54 | export const signIn = (user, history) => dispatch => {
55 | dispatch(loading());
56 | axios.post(
57 | 'https://cruzz.herokuapp.com/api/authentication/users/login',
58 | user
59 | ).then(res => {
60 | // Save / Set token to local storage
61 | console.log(res.data);
62 | let { token, username } = res.data.user;
63 | token = 'Token ' + token;
64 | localStorage.setItem('jwtToken', token);
65 | localStorage.setItem('username', username);
66 | // Set auth header
67 | setAuthToken(token);
68 | axios.get(`https://cruzz.herokuapp.com/api/profile/retrieve/${username}/`).then(response => {
69 | console.log(response.data);
70 | dispatch({ type: SET_CURRENT_USER, payload: response.data.user });
71 | dispatch({ type: ERROR, payload: '' });
72 | if(response.data.user) {
73 | history.push('/');
74 | }
75 | }).catch(err => console.log(err.response));
76 | }).catch(err => {
77 | dispatch({ type: ERROR, payload: err.response.data.errors });
78 | console.log(err.response)
79 | dispatch(loaded());
80 | });
81 | };
82 |
83 | export const signOut = history => dispatch => {
84 | dispatch(loading());
85 | // Remove token from local storage
86 | localStorage.removeItem('jwtToken');
87 | // Remove username from local storage
88 | localStorage.removeItem('username');
89 | // Remove auth header
90 | setAuthToken(false);
91 | // Set current user to {}
92 | dispatch({ type: SET_CURRENT_USER, payload: {} });
93 | if (history) history.push('/');
94 | else window.location.href = '/';
95 | }
96 |
97 | export const setCurrentUser = username => dispatch => {
98 | dispatch(loading());
99 | axios.get(`https://cruzz.herokuapp.com/api/profile/retrieve/${username}/`).then(response => {
100 | dispatch({ type: SET_CURRENT_USER, payload: response.data.user });
101 | dispatch({ type: SET_CURRENT_PROFILE, payload: response.data.profile });
102 | dispatch(loaded());
103 | }).catch(err => console.log(err.response));
104 | };
105 |
106 | export const authenticated = userToken => {
107 | return {
108 | type: AUTHENTICATED,
109 | payload: !isEmpty(userToken)
110 | };
111 | };
112 |
113 | export const loading = () => {
114 | return {
115 | type: LOADING
116 | };
117 | };
118 |
119 | export const loaded = () => {
120 | return {
121 | type: LOADED
122 | };
123 | };
124 |
--------------------------------------------------------------------------------
/src/actions/types.js:
--------------------------------------------------------------------------------
1 | export const SET_CURRENT_USER = 'SET_CURRENT_USER';
2 | export const SET_CURRENT_PROFILE = 'SET_CURRENT_PROFILE';
3 | export const USER_PROFILE = 'USER_PROFILE';
4 | export const SIGNUP = 'SIGNUP';
5 | export const ERROR = 'ERROR';
6 | export const LOADING = 'LOADING';
7 | export const LOADED = 'LOADED';
8 | export const AUTHENTICATED = 'AUTHENTICATED';
9 |
--------------------------------------------------------------------------------
/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Link , withRouter } from 'react-router-dom';
4 |
5 | import '../static/css/style.css';
6 | import logo from '../static/img/index.svg';
7 | import { loading, loaded } from './../actions/authActions';
8 | import axios from 'axios';
9 | import PostFeed from './feed/PostFeed';
10 | import FeedImg from '../static/img/no-feeds.png';
11 |
12 |
13 | class App extends Component {
14 |
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | posts: {}
19 | }
20 | this.fetchPosts = this.fetchPosts.bind(this);
21 | }
22 |
23 | componentDidMount() {
24 | this.fetchPosts();
25 | }
26 |
27 | fetchPosts() {
28 | axios.get('https://cruzz.herokuapp.com/api/post/feed/?&limit=100&offset=0/')
29 | .then(res => {
30 | this.setState({
31 | posts: res.data.posts.reverse()
32 | });
33 | console.log(res.data.posts)
34 | }).catch(err => {
35 | console.log(err.response);
36 | });
37 | }
38 |
39 | render() {
40 | let spinner;
41 |
42 | if (!this.props.auth.loading) {
43 | spinner = null;
44 | } else {
45 | spinner = (
46 |
47 | );
48 | }
49 | return (
50 |
51 |
52 | {spinner}
53 |
54 |
55 |
56 | {
57 | this.state.posts.length > 0 ? (
58 |
59 | {this.state.posts.map((post, key) => {
60 | return (
61 |
64 | )
65 | })}
66 |
67 | ):(
68 |
69 |
70 |

71 |
72 |
73 | )
74 | }
75 |
76 |
77 | {
78 | !this.props.auth.loading && this.state.posts.length === 0? (
79 |
80 |
81 | You don't have any posts in your feed, yet.
82 |
83 |

84 |
85 | Start Creating One
86 |
87 |
88 |
89 | ): null
90 | }
91 |
92 | );
93 | }
94 | }
95 |
96 | const mapStateToProps = state => ({
97 | auth: state.auth
98 | });
99 |
100 | export default connect(mapStateToProps, {loading, loaded})(withRouter(App));
101 |
--------------------------------------------------------------------------------
/src/components/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Route, Redirect } from 'react-router-dom';
4 | import PropTypes from 'prop-types';
5 |
6 | const PrivateRoute = ({ component: Component, auth, ...rest }) => (
7 | (auth.authenticated ? : )}
10 | />
11 | );
12 |
13 | PrivateRoute.propTypes = {
14 | auth: PropTypes.object.isRequired
15 | };
16 |
17 | const mapStateToProps = state => ({
18 | auth: state.auth
19 | });
20 |
21 | export default connect(mapStateToProps)(PrivateRoute);
22 |
--------------------------------------------------------------------------------
/src/components/common/Followers.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import axios from 'axios';
5 |
6 | import { loading, loaded } from '../../actions/authActions';
7 |
8 | class Followers extends Component {
9 |
10 | constructor(props) {
11 | super(props);
12 | this.state = ({
13 | username: null,
14 | follow: null,
15 | followers: {},
16 | })
17 | }
18 |
19 | componentDidMount() {
20 | const { username, follow } = this.props.match.params;
21 | const URI = 'https://cruzz.herokuapp.com/api/profile/' + follow + '/?user=' + username + '&limit=100&offset=0';
22 | this.props.loading();
23 | axios.get(URI).then(response => {
24 | console.log(response.data);
25 | this.setState({
26 | username: username,
27 | follow: follow,
28 | followers: response.data.profiles
29 | });
30 | this.props.loaded();
31 | }).catch(err => {
32 | console.log(err.response);
33 | this.props.loaded();
34 | });
35 | console.log(username);
36 | }
37 |
38 | render() {
39 |
40 | return (
41 |
42 |
43 |
44 |
45 |
46 | {this.state.follow}
47 |
48 |
49 |
50 | {
51 | this.state.followers.length > 0 ? (
52 |
53 | {this.state.followers.map((f, key) => {
54 | return (
55 |
56 |
-
57 |
58 |
59 |
60 |

61 |
62 |
63 |
{f.first_name}
64 |
{f.bio}
65 |
66 |
67 |
68 |
69 |
70 | )
71 | })}
72 |
73 | ):(
74 |
75 |
76 | {
77 | this.state.followers.length === 0 ?
78 | (
No followers yet ;(
) :
79 | (null)
80 | }
81 |
82 |
83 | )
84 | }
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 | }
94 |
95 | const mapStateToProps = state => ({
96 | auth: state.auth
97 | });
98 |
99 | export default connect(mapStateToProps, { loading, loaded })(withRouter(Followers));
100 |
--------------------------------------------------------------------------------
/src/components/common/Header.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 | import { connect } from "react-redux";
4 |
5 | import '../../static/css/common.css';
6 | import logo from '../../static/img/index.svg';
7 | import logoNoSpin from '../../static/img/index-no-spin.svg';
8 |
9 | import { signOut } from './../../actions/authActions';
10 |
11 | class Header extends Component {
12 | signOut() {
13 | this.props.signOut(this.props.history);
14 | }
15 |
16 | searchTag(event) {
17 | this.props.history.push("/posts/bytag/" + this.refs.searchString.value);
18 | event.preventDefault();
19 | }
20 |
21 | render() {
22 | let spinner;
23 |
24 | if (!this.props.auth.loading) {
25 | spinner = (
26 |
27 | -
28 |
29 |
30 |
31 | );
32 | } else {
33 | spinner = (
34 |
35 | -
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | return (
43 |
44 |
104 |
105 |
106 |
107 |
108 |
109 |
Notifications
110 |
111 |
Comming soon.
112 |
113 |
114 |
115 |
116 | );
117 | }
118 | }
119 |
120 | const mapStateToProps = state => ({
121 | auth: state.auth
122 | });
123 |
124 | export default connect(mapStateToProps, {signOut})(withRouter(Header));
125 |
--------------------------------------------------------------------------------
/src/components/common/PageSuggestions.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 |
4 | import '../../static/css/common.css';
5 | // import spinner from '../../static/img/index.svg';
6 | import { connect } from 'react-redux';
7 | import { loading, loaded } from './../../actions/authActions';
8 | import axios from 'axios';
9 |
10 | class PageSuggestions extends Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = ({
15 | full: true,
16 | suggestedPages: {}
17 | });
18 | this.suggestPages = this.suggestPages.bind(this);
19 | }
20 |
21 | componentDidMount() {
22 | this.suggestPages();
23 | if(this.props.full === false) {
24 | this.setState({
25 | full: this.props.full
26 | });
27 | }
28 | }
29 |
30 | suggestPages() {
31 | const URI = 'https://cruzz.herokuapp.com/api/profile/discover/pages/';
32 | axios.get(URI).then(res => {
33 | this.setState({
34 | suggestedPages: res.data.profiles.slice(0,2)
35 | })
36 | }).catch(err => {
37 | console.log(err.response);
38 | });
39 | }
40 |
41 | render() {
42 | return (
43 |
44 |
45 |
46 |
47 |
Official Pages You might
48 |
49 |
50 |
51 | {
52 | this.state.suggestedPages.length > 0 ? (
53 |
54 | {this.state.suggestedPages.map((f, key) => {
55 | return (
56 |
57 | {
58 | this.props.auth.user.username !== f.username ? (
59 | -
60 |
61 |
62 |
63 |

64 |
65 |
66 |
{f.first_name}
67 |
{f.bio}
68 |
69 |
70 |
71 |
72 | ): null
73 | }
74 |
75 | )
76 | })}
77 |
78 | ):(
79 |
80 |
81 | {
82 | this.state.suggestedPages.length === 0 ?
83 | (
No suggestions for you
) :
84 | (null)
85 | }
86 |
87 |
88 | )
89 | }
90 |
91 |
92 |
93 | {
94 | !this.state.full ? (
95 |
96 | Discover more
97 |
98 | ):null
99 | }
100 |
101 |
102 |
103 | );
104 | }
105 | }
106 |
107 | const mapStateToProps = state => ({
108 | auth: state.auth
109 | });
110 |
111 | export default connect(mapStateToProps, { loading, loaded })(withRouter(PageSuggestions));
112 |
--------------------------------------------------------------------------------
/src/components/common/PostOperations.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { withRouter } from 'react-router-dom';
4 |
5 | import { loading, loaded } from './../../actions/authActions';
6 | import axios from 'axios';
7 |
8 | class PostOperations extends Component {
9 | constructor (props) {
10 | super(props);
11 | this.state = ({
12 | operation: "new",
13 | slug: null,
14 | post: {
15 | author: {}
16 | },
17 | postBody: "Post Body",
18 | tagList: []
19 | });
20 | this.handleChange = this.handleChange.bind(this);
21 | this.handleTags = this.handleTags.bind(this);
22 | this.handlePost = this.handlePost.bind(this);
23 | this.getPostData = this.getPostData.bind(this);
24 | }
25 |
26 | componentDidMount () {
27 | this.setState({
28 | operation: this.props.match.params.operation,
29 | slug: this.props.match.params.slug
30 | });
31 | if(this.props.match.params.operation === 'edit') {
32 | this.getPostData();
33 | }
34 | }
35 |
36 | handleChange(event) {
37 | this.setState({postBody: event.target.value});
38 | }
39 |
40 | handleTags(event) {
41 | this.setState({tagList: event.target.value.split(',')});
42 | }
43 |
44 | getPostData () {
45 | this.props.loading();
46 | axios.get('https://cruzz.herokuapp.com/api/post/view/' + this.props.match.params.slug)
47 | .then(res => {
48 | this.setState({
49 | post: res.data.post,
50 | postBody: res.data.post.body,
51 | tagList: res.data.post.tagList
52 | });
53 | this.props.loaded();
54 | }).catch(err => {
55 | console.log(err.response);
56 | this.props.loaded();
57 | });
58 | this.props.loaded();
59 | }
60 |
61 | handlePost(event) {
62 | this.props.loading();
63 | const createPostURI = 'https://cruzz.herokuapp.com/api/post/create/';
64 | const editPostURI = 'https://cruzz.herokuapp.com/api/post/update/' + this.state.slug + '/';
65 | let handlePostURI = '';
66 | if(this.state.operation === 'new') {
67 | handlePostURI = createPostURI;
68 | } else {
69 | handlePostURI = editPostURI;
70 | }
71 | const postData = {
72 | post: {
73 | title: this.refs.postTitle.value,
74 | body: this.refs.postBody.value,
75 | tagList: this.state.tagList
76 | }
77 | };
78 | axios.post(handlePostURI, postData)
79 | .then(res => {
80 | this.setState({
81 | post: res.data.post
82 | });
83 | console.log(res.data);
84 | this.props.history.push('/view/post/' + res.data.post.slug);
85 | this.props.loaded();
86 | }).catch(err => {
87 | console.log(postData);
88 | console.log(err.response);
89 | this.props.loaded();
90 | });
91 | this.props.loaded();
92 | event.preventDefault();
93 | }
94 |
95 | render () {
96 | return (
97 |
98 |
99 |
100 |
151 |
152 |
153 |
👉🏼 Guidelines for posts
154 |
155 | - No use of bad language
156 | - No spamming
157 |
158 |
159 |
160 |
161 | )
162 | }
163 | }
164 |
165 | const mapStateToProps = state => ({
166 | auth: state.auth
167 | });
168 |
169 | export default connect(mapStateToProps, {loading, loaded})(withRouter(PostOperations));
170 |
--------------------------------------------------------------------------------
/src/components/common/SignIn.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import { connect } from 'react-redux';
4 |
5 | import { signIn } from '../../actions/authActions';
6 |
7 | import '../../static/css/common.css';
8 |
9 | class SignIn extends Component {
10 | signIn(event) {
11 | event.preventDefault();
12 | if(this.refs.username.value && this.refs.password.value) {
13 | const user = {
14 | user: {
15 | "username": this.refs.username.value,
16 | "password": this.refs.password.value,
17 | }
18 | }
19 | this.props.signIn(user, this.props.history);
20 | }
21 | }
22 |
23 | componentDidMount() {
24 | if (this.props.auth.authenticated) {
25 | this.props.history.push('/profile');
26 | }
27 | }
28 |
29 | render() {
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Sign In
39 |
40 |
41 |
42 |
43 |
44 |
69 |
70 |
71 |
72 |
73 | Not a user? Click here to new create a account.
74 |
75 |
76 |
77 |
78 |
79 | );
80 | }
81 | }
82 |
83 | const mapStateToProps = state => ({
84 | auth: state.auth
85 | });
86 |
87 | export default connect(mapStateToProps, { signIn })(SignIn);
88 |
--------------------------------------------------------------------------------
/src/components/common/SignUp.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link } from "react-router-dom";
3 | import { connect } from 'react-redux';
4 |
5 | import { signUp } from '../../actions/authActions';
6 |
7 | import '../../static/css/common.css';
8 |
9 | class SignUp extends Component {
10 |
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | mailError: '',
15 | passwordError: '',
16 | };
17 | this.handleMail = this.handleMail.bind(this);
18 | this.signUp = this.signUp.bind(this);
19 | this.handlePassword = this.handlePassword.bind(this);
20 | }
21 |
22 | handleMail(event) {
23 | const allowedDomain = 'iiitvadodara.ac.in'
24 | if(event.target.value.split('@')[1] !== allowedDomain) {
25 | this.setState({ mailError: 'Please use a @iiitvadodara.ac.in email'});
26 | } else {
27 | this.setState({ mailError: ''});
28 | }
29 | }
30 |
31 | handlePassword(event) {
32 | if(event.target.value !== this.refs.password.value) {
33 | this.setState({ passwordError: 'Password did not match!'});
34 | } else {
35 | this.setState({ passwordError: '' });
36 | }
37 | }
38 |
39 | signUp(event) {
40 | event.preventDefault();
41 | if (this.refs.password.value === this.refs.confirmPassword.value) {
42 | const user = {
43 | user: {
44 | "first_name": this.refs.first_name.value,
45 | "last_name": this.refs.last_name.value,
46 | "email": this.refs.email.value,
47 | "username": this.refs.username.value,
48 | "password": this.refs.password.value
49 | }
50 | }
51 | this.props.signUp(user, this.props.history);
52 | } else {
53 | this.setState({
54 | passwordError: 'Passwords did not match!'
55 | });
56 | }
57 | }
58 |
59 | render() {
60 | return (
61 |
62 |
63 |
64 |
65 |
66 |
Sign Up
67 |
68 |
69 |
161 |
162 |
163 |
Already have an account, Click here to sign in.
164 |
165 |
166 |
167 |
168 | );
169 | }
170 | }
171 |
172 | const mapStateToProps = state => ({
173 | auth: state.auth
174 | });
175 |
176 | export default connect(mapStateToProps, { signUp })(SignUp);
177 |
--------------------------------------------------------------------------------
/src/components/feed/PostDetails.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import { Link, withRouter } from 'react-router-dom';
4 |
5 | import { loading, loaded } from './../../actions/authActions';
6 | import axios from 'axios';
7 | import PostFeed from "./PostFeed";
8 |
9 | import spinner from '../../static/img/index.svg';
10 |
11 |
12 | class PostDetails extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = ({
16 | posts: [],
17 | comments: [],
18 | commentBody: ''
19 | });
20 | this.getComments = this.getComments.bind(this);
21 | this.handleComment = this.handleComment.bind(this);
22 | this.handleCommentBody = this.handleCommentBody.bind(this);
23 | }
24 |
25 | componentDidMount() {
26 | this.props.loading();
27 | axios.get('https://cruzz.herokuapp.com/api/post/view/' + this.props.match.params.slug + '/')
28 | .then(res => {
29 | this.setState({
30 | posts: [res.data.post]
31 | });
32 | this.props.loaded();
33 | this.getComments();
34 | }).catch(err => {
35 | console.log(err.response);
36 | this.props.loaded();
37 | });
38 | }
39 |
40 | handleCommentBody(event) {
41 | this.setState({
42 | commentBody: event.target.value
43 | });
44 | event.preventDefault();
45 | }
46 |
47 | deleteComment(event, pk) {
48 | const delURI = 'https://cruzz.herokuapp.com/api/post/' + this.props.match.params.slug + '/comments/delete/' + pk + '/';
49 | axios.get(delURI).then(res => {
50 | this.getComments();
51 | }).catch(err => {
52 | console.log(err.response);
53 | });
54 | event.preventDefault();
55 | }
56 |
57 | editComment(event, pk) {
58 | const edtURI = 'https://cruzz.herokuapp.com/api/post/' + this.props.match.params.slug + '/comments/update/' + pk + '/';
59 | let comment = {
60 | comment : {
61 | body: this.state.commentBody
62 | }
63 | }
64 | this.props.loading()
65 | axios.post(edtURI, comment).then(res => {
66 | this.setState({
67 | commentBody: ''
68 | });
69 | this.getComments();
70 | }).catch(err => {
71 | console.log(err.response);
72 | this.props.loaded()
73 | });
74 |
75 | event.preventDefault();
76 | }
77 |
78 | handleComment(event) {
79 |
80 | const createURI = 'https://cruzz.herokuapp.com/api/post/' + this.props.match.params.slug + '/comments/create/';
81 | let comment = {
82 | comment : {
83 | body: this.state.commentBody
84 | }
85 | }
86 | axios.post(createURI, comment).then(res => {
87 | this.setState({
88 | commentBody: ''
89 | });
90 | this.getComments();
91 | }).catch(err => {
92 | console.log(err.response);
93 | });
94 | event.preventDefault();
95 | }
96 |
97 | getComments() {
98 | this.props.loading()
99 | const URI = 'https://cruzz.herokuapp.com/api/post/' + this.props.match.params.slug + '/comments/view?limit=100&offset=0';
100 | axios.get(URI).then(res => {
101 | this.setState({
102 | comments: res.data.comments.reverse()
103 | });
104 | this.props.loaded()
105 | }).catch(err => {
106 | console.log(err.response);
107 | this.props.loaded()
108 | });
109 | }
110 |
111 | handleDateTime(date) {
112 | const dateLocal = new Date(date);
113 | const timeLocal = dateLocal.toLocaleTimeString();
114 | return (String(dateLocal.toDateString()) + " " + timeLocal);
115 | }
116 |
117 | render() {
118 | return (
119 |
120 | {
121 | this.state.posts.length > 0 ? (
122 |
123 | {this.state.posts.map((post, key) => {
124 | return (
125 |
134 | )
135 | })}
136 |
137 | ):(
138 |
139 |

140 |
141 | )
142 | }
143 |
144 |
145 | {
146 | this.state.comments.length > 0 ? (
147 |
148 | {this.state.comments.map((comment, key) => {
149 | return (
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |

158 |
159 |
160 |
{comment.author.first_name}
164 |
165 | {
166 | comment.author.username === this.state.posts[0].author.username ? (
167 |
168 | ) : null
169 | }
170 | {
171 | comment.author.official_page ? (
172 |
173 | ): null
174 | }
175 |
176 |
177 |
178 |
179 |
180 |
181 |
{comment.body}
182 |
183 |
184 |
185 | {
186 | this.props.auth.user.username === comment.author.username ? (
187 |
188 |
189 |
190 |
208 |
209 |
): null
210 | }
211 | {
212 | this.props.auth.user.username === comment.author.username ? (
213 |
214 | this.deleteComment(e, comment.id)} data-uk-icon="trash" data-uk-tooltip="title: delete; pos: bottom-center">
215 |
216 | ): null
217 | }
218 |
219 |
220 |
221 |
222 | )
223 | })}
224 |
225 | ):null
226 | }
227 |
228 |
229 | )
230 | }
231 | }
232 |
233 | const mapStateToProps = state => ({
234 | auth: state.auth
235 | });
236 |
237 | export default connect(mapStateToProps, {loading, loaded})(withRouter(PostDetails));
238 |
--------------------------------------------------------------------------------
/src/components/feed/PostFeed.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link, withRouter } from "react-router-dom";
3 | import { connect } from 'react-redux';
4 | import axios from 'axios';
5 | import { loading, loaded } from './../../actions/authActions';
6 |
7 | class PostFeed extends Component {
8 | constructor(props) {
9 | super(props);
10 | // Don't call this.setState() here!
11 | this.state = {
12 | upvotes: 0,
13 | downvotes: 0,
14 | comments: 0,
15 | shares: 0,
16 | post: {
17 | author: {},
18 | tagList: []
19 | }
20 | };
21 | this.upVote = this.upVote.bind(this);
22 | this.downVote = this.downVote.bind(this);
23 | this.favoritePost = this.favoritePost.bind(this);
24 | this.share = this.share.bind(this);
25 | this.deletePost = this.deletePost.bind(this);
26 | this.handleDateTime = this.handleDateTime.bind(this);
27 | }
28 |
29 | dangerous(html) {
30 | return {
31 | __html: html
32 | }
33 | }
34 | upVote(e) {
35 | this.props.loading();
36 | const URI = 'https://cruzz.herokuapp.com/api/post/' + this.state.post.slug + '/upvote/';
37 | if(this.state.post.upvoted) {
38 | axios.delete(URI).then(res => {
39 | this.setState({
40 | post: res.data.post
41 | });
42 | this.props.loaded();
43 | }).catch(err => {
44 | console.log(err.response);
45 | this.props.loaded();
46 | });
47 | } else {
48 | axios.get(URI).then(res => {
49 | console.log(res.data);
50 | this.setState({
51 | post: res.data.post
52 | });
53 | if(this.state.post.downvoted) {
54 | this.downVote();
55 | }
56 | }).catch(err => {
57 | console.log(err.response);
58 | });
59 | this.props.loaded();
60 | }
61 | }
62 |
63 | downVote(e) {
64 | const URI = 'https://cruzz.herokuapp.com/api/post/' + this.state.post.slug + '/downvote/';
65 | if(this.state.post.downvoted) {
66 | axios.delete(URI).then(res => {
67 | this.setState({
68 | post: res.data.post
69 | });
70 | this.props.loaded();
71 | }).catch(err => {
72 | console.log(err.response);
73 | this.props.loaded();
74 | });
75 | } else {
76 | axios.get(URI).then(res => {
77 | this.setState({
78 | post: res.data.post
79 | });
80 | if(this.state.post.upvoted) {
81 | this.upVote();
82 | }
83 | }).catch(err => {
84 | console.log(err.response);
85 | });
86 | }
87 | }
88 |
89 | favoritePost(e) {
90 | const URI = 'https://cruzz.herokuapp.com/api/post/' + this.state.post.slug + '/favorite/';
91 | if(this.state.post.favorited) {
92 | axios.delete(URI).then(res => {
93 | this.setState({
94 | post: res.data.post
95 | });
96 | }).catch(err => {
97 | console.log(err.response);
98 | });
99 | } else {
100 | axios.get(URI).then(res => {
101 | console.log(res.data);
102 | this.setState({
103 | post: res.data.post
104 | });
105 | }).catch(err => {
106 | console.log(err.response);
107 | });
108 | }
109 | }
110 |
111 | share(e) {
112 | // console.log(e);
113 | let shares = this.state.shares;
114 | this.setState({ shares: shares + 1});
115 | }
116 |
117 | componentDidMount() {
118 | this.setState({
119 | post: this.props.post
120 | });
121 | }
122 |
123 | deletePost() {
124 | const URI = 'https://cruzz.herokuapp.com/api/post/' + this.state.post.slug + '/delete/'
125 | axios.get(URI).then(res => {
126 | console.log(res.data);
127 | this.props.history.push('/');
128 | }).catch(err => {
129 | console.log(err.response);
130 | })
131 | }
132 |
133 | handleDateTime(date) {
134 | const dateLocal = new Date(date);
135 | let mnth = ("0" + (dateLocal.getMonth()+1)).slice(-2);
136 | let day = ("0" + dateLocal.getDate()).slice(-2);
137 | const dateLocalFormatted = [dateLocal.getFullYear(), mnth, day ].join("-");
138 | const todayDate = new Date();
139 | mnth = ("0" + (todayDate.getMonth()+1)).slice(-2);
140 | day = ("0" + todayDate.getDate()).slice(-2);
141 | const todayDateFormatted = [todayDate.getFullYear(), mnth, day ].join("-");
142 | const timeLocal = dateLocal.toLocaleTimeString();
143 | if(dateLocalFormatted=== todayDateFormatted)
144 | {
145 | return (timeLocal);
146 | }
147 | else{
148 | return (String(dateLocal.toDateString().slice(3)));
149 | }
150 | }
151 |
152 |
153 |
154 | render() {
155 | return (
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |

164 |
165 |
166 |
167 |
168 | {this.state.post.title}
169 |
170 |
171 |
172 | {this.state.post.author.first_name}
176 |
177 | posted on
178 |
179 |
180 |
181 |
182 |
183 |
184 | {this.state.post.tagList.map(
185 | (tag, key) => {
186 | return(
187 |
192 | )
193 | }
194 | )}
195 |
196 |
197 |
198 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | {this.state.post.upvotesCount}
209 |
210 |
211 |
212 |
213 | {this.state.post.downvotesCount}
214 |
215 |
216 |
217 |
218 | {this.state.post.favoritesCount}
219 |
220 |
221 |
222 | {
223 | this.props.full ?
224 | (
225 |
226 | ): (
227 |
228 | )
229 | }
230 | {this.state.post.commentsCount}
231 |
232 |
233 |
234 |
235 | {
236 | this.props.auth.user.username === this.state.post.author.username ?
237 | (
238 |
239 |
240 |
241 | ):null
242 | }
243 | {
244 | this.props.auth.user.username === this.state.post.author.username ?
245 | (
246 |
247 |
248 |
249 | ):null
250 | }
251 |
252 |
253 |
254 |
255 |
256 |
257 | );
258 | }
259 | }
260 |
261 | const mapStateToProps = state => ({
262 | auth: state.auth
263 | });
264 |
265 |
266 | export default connect(mapStateToProps, {loading, loaded})(withRouter(PostFeed));
267 |
--------------------------------------------------------------------------------
/src/components/feed/PostsByTags.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import axios from 'axios';
5 |
6 | import spinner from '../../static/img/index.svg';
7 | import { loading, loaded } from './../../actions/authActions';
8 | import PostFeed from './../feed/PostFeed';
9 |
10 | class PostsByTags extends Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = ({
15 | tag: null,
16 | posts: {},
17 | });
18 | this.fetchTaggedPosts = this.fetchTaggedPosts.bind(this);
19 | }
20 |
21 | componentDidUpdate(prevProps) {
22 | if(prevProps.match.params.tag !== this.props.match.params.tag) {
23 | window.location.reload();
24 | }
25 | }
26 |
27 | componentDidMount() {
28 | this.props.loading();
29 | this.fetchTaggedPosts(this.props.match.params.tag);
30 | }
31 |
32 | fetchTaggedPosts(tag) {
33 | axios.get('https://cruzz.herokuapp.com/api/post/view/?tag=' + tag + '&limit=100&offset=0/')
34 | .then(res => {
35 | this.setState({
36 | posts: res.data.posts
37 | });
38 | this.props.loaded();
39 | }).catch(err => {
40 | console.log(err.response);
41 | this.props.loaded()
42 | });
43 | }
44 |
45 | render() {
46 |
47 | return (
48 |
49 |
50 |
Posts tagged with "{this.props.match.params.tag}"
51 |
52 |
53 | {
54 | this.state.posts.length > 0 ? (
55 |
56 | {this.state.posts.map((post, key) => {
57 | return (
58 |
61 | )
62 | })}
63 |
64 | ):(
65 | this.props.auth.loading ? (
66 |
67 |
68 |

69 |
70 |
71 | ):(
72 |
73 |
74 |
75 | No posts
76 |
77 |
78 |
79 | )
80 | )
81 | }
82 |
83 |
84 |
85 |
86 | );
87 | }
88 | }
89 |
90 | const mapStateToProps = state => ({
91 | auth: state.auth
92 | });
93 |
94 | export default connect(mapStateToProps, { loading, loaded })(withRouter(PostsByTags));
95 |
--------------------------------------------------------------------------------
/src/components/profile/FavoritePosts.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { withRouter } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import axios from 'axios';
5 |
6 | import spinner from '../../static/img/index.svg';
7 | import { loading, loaded } from './../../actions/authActions';
8 | import PostFeed from './../feed/PostFeed';
9 |
10 | class FavoritePosts extends Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = ({
15 | username: null,
16 | posts: {},
17 | });
18 | this.fetchFavoritePosts = this.fetchFavoritePosts.bind(this);
19 | }
20 |
21 | componentDidMount() {
22 | this.props.loading();
23 | this.fetchFavoritePosts(this.props.match.params.username);
24 | }
25 |
26 | fetchFavoritePosts(username) {
27 | axios.get('https://cruzz.herokuapp.com/api/post/view/?favorited=' + username + '&limit=100&offset=0/')
28 | .then(res => {
29 | this.setState({
30 | posts: res.data.posts
31 | });
32 | this.props.loaded();
33 | }).catch(err => {
34 | console.log(err.response);
35 | this.props.loaded()
36 | });
37 | this.props.loaded()
38 | }
39 |
40 | render() {
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
48 | {
49 | this.state.posts.length > 0 ? (
50 |
51 | {this.state.posts.map((post, key) => {
52 | return (
53 |
56 | )
57 | })}
58 |
59 | ):(
60 |
61 |
62 |

63 |
64 |
65 | )
66 | }
67 |
68 |
69 |
70 |
71 | );
72 | }
73 | }
74 |
75 | const mapStateToProps = state => ({
76 | auth: state.auth
77 | });
78 |
79 | export default connect(mapStateToProps, { loading, loaded })(withRouter(FavoritePosts));
80 |
--------------------------------------------------------------------------------
/src/components/profile/ProfilePage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 | import { connect } from "react-redux";
4 |
5 | import fireStorage from "../../firebase";
6 |
7 | import coverPhoto from '../../static/img/retro-hop.jpg';
8 | import spinner from '../../static/img/index.svg';
9 | import PageSuggestions from "../common/PageSuggestions";
10 | import { updateUserProfile } from '../../actions/authActions';
11 | import { loading, loaded } from './../../actions/authActions';
12 |
13 | import axios from 'axios';
14 | import PostFeed from "../feed/PostFeed";
15 |
16 | class ProfilePage extends Component {
17 |
18 | constructor(props) {
19 | super(props);
20 | this.state = ({
21 | suggestedPages: {},
22 | posts: {},
23 | following : {},
24 | followers: {}
25 | });
26 | this.fetchPosts = this.fetchPosts.bind(this);
27 | this.fetchFollowers = this.fetchFollowers.bind(this);
28 | this.fetchFollowing = this.fetchFollowing.bind(this);
29 | }
30 |
31 | componentDidMount() {
32 | this.fetchPosts(this.props.match.params.username);
33 | this.fetchFollowing(this.props.match.params.username);
34 | this.fetchFollowers(this.props.match.params.username);
35 | }
36 |
37 | fetchFollowing(username) {
38 | const URI = 'https://cruzz.herokuapp.com/api/profile/following/?user=' + username + '&limit=100&offset=0';
39 | axios.get(URI).then(res => {
40 | this.setState({
41 | following: res.data.profiles.slice(0, 2)
42 | })
43 | }).catch(err => {
44 | console.log(err.response);
45 | });
46 | }
47 |
48 | fetchFollowers(username) {
49 | const URI = 'https://cruzz.herokuapp.com/api/profile/followers/?user=' + username + '&limit=100&offset=0';
50 | axios.get(URI).then(res => {
51 | this.setState({
52 | followers: res.data.profiles.slice(0, 2)
53 | })
54 | }).catch(err => {
55 | console.log(err.response);
56 | });
57 | }
58 |
59 | fetchPosts(username) {
60 | axios.get('https://cruzz.herokuapp.com/api/post/view/?author=' + username + '&limit=100&offset=0/')
61 | .then(res => {
62 | this.setState({
63 | posts: res.data.posts
64 | });
65 | }).catch(err => {
66 | console.log(err.response);
67 | });
68 | }
69 |
70 | updateProfile(event) {
71 | let user = {};
72 | if(this.refs.new_first_name.value) {
73 | user.first_name = this.refs.new_first_name.value;
74 | }
75 | if(this.refs.new_last_name.value) {
76 | user.last_name = this.refs.new_last_name.value;
77 | }
78 | if(this.refs.new_email.value) {
79 | user.email = this.refs.new_email.value;
80 | }
81 | if(this.refs.new_bio.value) {
82 | user.bio = this.refs.new_bio.value;
83 | }
84 | const updatedUser = {
85 | user: user
86 | }
87 | this.props.updateUserProfile(updatedUser, this.props.history);
88 | event.preventDefault();
89 | }
90 |
91 | uploadCoverPic(event) {
92 | console.log(event.target.files[0]);
93 | const image = event.target.files[0];
94 | // console.log(image.name);
95 | let uploadTask = fireStorage.ref(`covers/${image.name}`).put(image);
96 | uploadTask.on('state_changed',
97 | (snapshot) => {
98 | this.props.loading();
99 | console.log("uploading");
100 | },
101 | (error) => {
102 | this.props.loaded();
103 | console.log(error);
104 | },
105 | () => {
106 | fireStorage.ref('covers').child(image.name).getDownloadURL().then(
107 | url => {
108 | console.log(url);
109 | const updatedUser = {
110 | user : {
111 | cover: url
112 | }
113 | };
114 | this.props.updateUserProfile(updatedUser, this.props.history);
115 | }
116 | );
117 | }
118 | )
119 | event.preventDefault();
120 | }
121 |
122 | uploadProfilePic(event) {
123 | console.log(event.target.files[0]);
124 | const image = event.target.files[0];
125 | // console.log(image.name);
126 | let uploadTask = fireStorage.ref(`dp/${image.name}`).put(image);
127 | uploadTask.on('state_changed',
128 | (snapshot) => {
129 | console.log("uploading");
130 | this.props.loading();
131 | },
132 | (error) => {
133 | console.log(error);
134 | this.props.loaded();
135 | },
136 | () => {
137 | fireStorage.ref('dp').child(image.name).getDownloadURL().then(
138 | url => {
139 | const updatedUser = {
140 | user : {
141 | image: url
142 | }
143 | };
144 | this.props.updateUserProfile(updatedUser, this.props.history);
145 | }
146 | );
147 | }
148 | )
149 | event.preventDefault();
150 | }
151 |
152 | updateProfilePic() {
153 | document.getElementById("dp").click();
154 | }
155 |
156 | updateCoverPic() {
157 | document.getElementById("cover").click();
158 | }
159 |
160 | render() {
161 |
162 | return (
163 |
164 |
165 |
166 |
167 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |

this.updateProfilePic(e)} alt="" data-uk-tooltip="title: Upload new Profile Picture; pos: bottom-center"/>
179 | {
180 | this.props.auth.userProfile.official_page ? (
181 |
182 | ): null
183 | }
184 |
185 |
188 | {
189 | !this.props.auth.loading ?
190 | (
191 |
this.updateProfilePic(e)} uk-icon="icon: cloud-upload; ratio: 3">
192 | ):
193 | }
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 | {this.props.auth.user.first_name ? this.props.auth.user.first_name: "Name"}
203 |
204 | {this.props.auth.user.last_name ? this.props.auth.user.last_name: "Last"}
205 |
206 | {this.props.auth.user.bio ? this.props.auth.user.bio: "dattebayo! ✌"}
207 |
208 |
209 |
210 |
211 |
212 |
213 |
251 |
252 |
253 |
254 |
255 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 | Create a new post
274 |
275 |
276 |
277 |
278 |
279 | Show your favorite posts
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 | - Following - {this.props.auth.userProfile.followingCount}
289 | - Followers - {this.props.auth.userProfile.followersCount}
290 |
291 |
292 |
293 | {
294 | this.state.following.length > 0 ? (
295 |
296 | {this.state.following.map((f, key) => {
297 | return (
298 |
299 |
-
300 |
301 |
302 |
303 |

304 |
305 |
306 |
{f.first_name}
307 |
{f.bio}
308 |
309 |
310 |
311 |
312 |
313 | )
314 | })}
315 |
316 | ):(
317 |
318 |
319 | {
320 | this.state.following.length === 0 ?
321 | (
Not following anyone yet ;(
) :
322 | (
)
323 | }
324 |
325 |
326 | )
327 | }
328 | {
329 | this.state.following.length > 0 ?
330 | (
Show more people you follow)
331 | : null
332 | }
333 |
334 |
335 | {
336 | this.state.followers.length > 0 ? (
337 |
338 | {this.state.followers.map((f, key) => {
339 | return (
340 |
341 |
-
342 |
343 |
344 |
345 |

346 |
347 |
348 |
{f.first_name}
349 |
{f.bio}
350 |
351 |
352 |
353 |
354 |
355 | )
356 | })}
357 |
358 | ):(
359 |
360 |
361 | {
362 | this.state.followers.length === 0 ?
363 | (
No followers yet ;(
) :
364 | (
)
365 | }
366 |
367 |
368 | )
369 | }
370 | {
371 | this.state.followers.length > 0 ?
372 | (
Show all your followers)
373 | : null
374 | }
375 |
376 |
377 |
378 |
379 |
380 |
383 |
384 |
385 |
386 |
387 | {
388 | this.state.posts.length > 0 ? (
389 |
390 | {this.state.posts.map((post, key) => {
391 | return (
392 |
395 | )
396 | })}
397 |
398 | ):(
399 |
400 |
401 |

402 |
403 |
404 | )
405 | }
406 |
407 |
408 |
409 |
410 |
411 | );
412 | }
413 | }
414 |
415 | const mapStateToProps = state => ({
416 | auth: state.auth
417 | });
418 |
419 | export default connect(mapStateToProps, { updateUserProfile, loading, loaded })(withRouter(ProfilePage));
420 |
--------------------------------------------------------------------------------
/src/components/profile/UserProfile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Link, withRouter } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import axios from 'axios';
5 |
6 | // import fireStorage from "../../firebase";
7 |
8 | import coverPhoto from '../../static/img/retro-hop.jpg';
9 | import spinner from '../../static/img/index.svg';
10 | // import avtar from '../../static/img/avtar.jpg'
11 | import PageSuggestions from "../common/PageSuggestions";
12 | import { loading, loaded } from './../../actions/authActions';
13 | import PostFeed from './../feed/PostFeed';
14 |
15 | class UserProfile extends Component {
16 |
17 | constructor(props) {
18 | super(props);
19 | this.state = ({
20 | username: null,
21 | suggestedPages: {},
22 | profile: {},
23 | posts: {},
24 | following : {},
25 | followers: {}
26 | });
27 | this.handleFollow = this.handleFollow.bind(this);
28 | this.fetchPosts = this.fetchPosts.bind(this);
29 | this.fetchFollowers = this.fetchFollowers.bind(this);
30 | this.fetchFollowing = this.fetchFollowing.bind(this);
31 | }
32 |
33 | componentDidUpdate(prevProps) {
34 | if(prevProps.match.params.username !== this.props.match.params.username) {
35 | window.location.reload();
36 | }
37 | }
38 |
39 | componentDidMount() {
40 | const { username } = this.props.match.params;
41 | console.log(username);
42 | this.props.loading();
43 | axios.get(`https://cruzz.herokuapp.com/api/profile/retrieve/${username}/`).then(response => {
44 | console.log(response.data);
45 | // dispatch({ type: SET_CURRENT_USER, payload: response.data.user });
46 | // dispatch({ type: SET_CURRENT_PROFILE, payload: response.data.profile });
47 | this.setState({
48 | username: username,
49 | profile: response.data.profile
50 | });
51 | this.props.loaded();
52 | }).catch(err => {
53 | console.log(err.response);
54 | this.props.loaded();
55 | });
56 | this.fetchPosts(this.props.match.params.username);
57 | this.fetchFollowing(this.props.match.params.username);
58 | this.fetchFollowers(this.props.match.params.username);
59 | }
60 |
61 | fetchFollowing(username) {
62 | const URI = 'https://cruzz.herokuapp.com/api/profile/following/?user=' + username + '&limit=100&offset=0';
63 | axios.get(URI).then(res => {
64 | this.setState({
65 | following: res.data.profiles.slice(0, 2)
66 | })
67 | }).catch(err => {
68 | console.log(err.response);
69 | });
70 | }
71 |
72 | fetchFollowers(username) {
73 | const URI = 'https://cruzz.herokuapp.com/api/profile/followers/?user=' + username + '&limit=100&offset=0';
74 | console.log(URI);
75 | axios.get(URI).then(res => {
76 | this.setState({
77 | followers: res.data.profiles.slice(0, 2)
78 | })
79 | }).catch(err => {
80 | console.log(err.response);
81 | });
82 | }
83 |
84 | fetchPosts(username) {
85 | axios.get('https://cruzz.herokuapp.com/api/post/view/?author=' + username + '&limit=100&offset=0/')
86 | .then(res => {
87 | this.setState({
88 | posts: res.data.posts
89 | });
90 | }).catch(err => {
91 | console.log(err.response);
92 | });
93 | }
94 |
95 | handleFollow() {
96 | this.props.loading();
97 | if(this.state.profile.following) {
98 | axios.delete('https://cruzz.herokuapp.com/api/profile/' + this.state.username + '/follow')
99 | .then(res => {
100 | console.log(res.data);
101 | this.setState({
102 | profile: res.data.profile
103 | });
104 | this.props.loaded();
105 | }).catch(err => {
106 | console.log(err.response);
107 | this.props.loaded();
108 | });
109 | } else {
110 | axios.post('https://cruzz.herokuapp.com/api/profile/' + this.state.username + '/follow')
111 | .then(res => {
112 | console.log(res.data);
113 | this.setState({
114 | profile: res.data.profile
115 | });
116 | this.props.loaded();
117 | }).catch(err => {
118 | console.log(err.response);
119 | this.props.loaded();
120 | });
121 | }
122 | this.props.loaded();
123 | }
124 |
125 | render() {
126 |
127 | return (
128 |
129 |
130 |
131 |
132 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |

144 | {
145 | this.state.profile.official_page ? (
146 |
147 | ): null
148 | }
149 |
150 | {
151 | !this.props.auth.loading ?
152 | (
153 |
154 | ):
155 | }
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | {this.state.profile.first_name ? this.state.profile.first_name: "Name"}
165 |
166 | {this.state.profile.last_name ? this.state.profile.last_name: "Last"}
167 |
168 | {this.state.profile.bio ? this.state.profile.bio: "dattebayo! ✌"}
169 |
170 | {
171 | this.state.profile.username !== this.props.auth.user.username ? (
172 |
173 | {
174 | this.state.profile.following ?
175 | ():
176 | ()
177 | }
178 |
179 | ): null
180 | }
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 | {/*
192 |
193 | New post
194 |
195 |
196 |
197 |
*/}
198 |
199 |
200 |
201 |
202 | - Following - {this.state.profile.followingCount}
203 | - Followers - {this.state.profile.followersCount}
204 |
205 |
206 |
207 | {
208 | this.state.following.length > 0 ? (
209 |
210 | {this.state.following.map((f, key) => {
211 | return (
212 |
213 |
-
214 |
215 |
216 |
217 |

218 |
219 |
220 |
{f.first_name}
221 |
{f.bio}
222 |
223 |
224 |
225 |
226 |
227 | )
228 | })}
229 |
230 | ):(
231 |
232 |
233 | {
234 | this.state.following.length === 0 ?
235 | (
Not following anyone yet ;(
) :
236 | (
237 |
238 | )
239 | }
240 |
241 |
242 | )
243 | }
244 | {
245 | this.state.following.length > 0 ?
246 | (
Show more)
247 | : null
248 | }
249 |
250 |
251 | {
252 | this.state.followers.length > 0 ? (
253 |
254 | {this.state.followers.map((f, key) => {
255 | return (
256 |
257 |
-
258 |
259 |
260 |
261 |

262 |
263 |
264 |
{f.first_name}
265 |
{f.bio}
266 |
267 |
268 |
269 |
270 |
271 | )
272 | })}
273 |
274 | ):(
275 |
276 |
277 | {
278 | this.state.followers.length === 0 ?
279 | (
No followers yet ;(
) :
280 | (

)
281 | }
282 |
283 |
284 | )
285 | }
286 | {
287 | this.state.followers.length > 0 ?
288 | (
Show more followers)
289 | : null
290 | }
291 |
292 |
293 |
294 |
295 |
296 |
299 |
300 |
301 |
302 |
303 | {
304 | this.state.posts.length > 0 ? (
305 |
306 | {this.state.posts.map((post, key) => {
307 | return (
308 |
311 | )
312 | })}
313 |
314 | ):(
315 | this.state.posts.length > 0 ? (
316 |
317 |
318 |

319 |
320 |
321 | ): (
322 |
323 |
324 |
No posts here ;(
325 |
326 |
327 | )
328 | )
329 | }
330 |
331 |
332 |
333 |
334 |
335 | );
336 | }
337 | }
338 |
339 | const mapStateToProps = state => ({
340 | auth: state.auth
341 | });
342 |
343 | export default connect(mapStateToProps, { loading, loaded })(withRouter(UserProfile));
344 |
--------------------------------------------------------------------------------
/src/firebase/index.js:
--------------------------------------------------------------------------------
1 | import firebase from "firebase/app";
2 | import "firebase/storage"
3 |
4 |
5 | // Initialize Firebase
6 | var config = {
7 | apiKey: "AIzaSyDjjAxLOgctgF3w1-BVIPfvf1JNZvG4XC0",
8 | authDomain: "cruzz-503cd.firebaseapp.com",
9 | databaseURL: "https://cruzz-503cd.firebaseio.com",
10 | projectId: "cruzz-503cd",
11 | storageBucket: "cruzz-503cd.appspot.com",
12 | messagingSenderId: "706067637651"
13 | };
14 | firebase.initializeApp(config);
15 |
16 | const fireStorage = firebase.storage();
17 |
18 | export {
19 | firebase, fireStorage as default
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Route } from "react-router";
4 | import { BrowserRouter, Switch } from "react-router-dom";
5 | import { Provider } from 'react-redux';
6 |
7 | import store from './store';
8 |
9 | import App from './components/App';
10 | import Header from './components/common/Header';
11 | import SignIn from './components/common/SignIn';
12 | import SignUp from './components/common/SignUp';
13 | import ProfilePage from './components/profile/ProfilePage';
14 | import PrivateRoute from './components/PrivateRoute';
15 |
16 | import registerServiceWorker from './registerServiceWorker';
17 |
18 | import { setCurrentUser, authenticated } from './actions/authActions';
19 | import setAuthToken from './utils/setAuthToken';
20 | import UserProfile from './components/profile/UserProfile';
21 | import Followers from './components/common/Followers';
22 | import PostOperations from './components/common/PostOperations';
23 | import PostDetails from './components/feed/PostDetails';
24 | import PageSuggestions from './components/common/PageSuggestions';
25 | import FavoritePosts from './components/profile/FavoritePosts';
26 | import PostsByTags from './components/feed/PostsByTags';
27 |
28 | if (localStorage.jwtToken) {
29 | // Set auth token header auth
30 | setAuthToken(localStorage.jwtToken);
31 | // Set current user
32 | store.dispatch(authenticated(localStorage.jwtToken));
33 | store.dispatch(setCurrentUser(localStorage.username));
34 | }
35 |
36 | ReactDOM.render(
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ,
58 | document.getElementById('root')
59 | );
60 | registerServiceWorker();
61 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/reducers/authReducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | // SIGNUP,
3 | SET_CURRENT_USER,
4 | SET_CURRENT_PROFILE,
5 | LOADING,
6 | LOADED,
7 | // USER_PROFILE,
8 | ERROR
9 | } from '../actions/types';
10 |
11 | import { isEmpty } from '../utils/validate';
12 | import { AUTHENTICATED } from './../actions/types';
13 |
14 | const initialState = {
15 | loading: false,
16 | authenticated: false,
17 | user: {},
18 | userProfile: {},
19 | errors: {
20 | error: []
21 | }
22 | };
23 |
24 | export default function(state = initialState, action) {
25 | switch (action.type) {
26 | case SET_CURRENT_USER:
27 | return {
28 | ...state,
29 | authenticated: !isEmpty(action.payload),
30 | user: action.payload,
31 | loading: false
32 | };
33 | case SET_CURRENT_PROFILE:
34 | return {
35 | ...state,
36 | authenticated: !isEmpty(action.payload),
37 | userProfile: action.payload,
38 | loading: false
39 | };
40 | case LOADING:
41 | return {
42 | ...state,
43 | loading: true
44 | };
45 | case LOADED:
46 | return {
47 | ...state,
48 | loading: false
49 | };
50 | case AUTHENTICATED:
51 | return {
52 | ...state,
53 | authenticated: action.payload
54 | };
55 | case ERROR:
56 | return {
57 | ...state,
58 | errors: action.payload
59 | };
60 | default:
61 | return state;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import authReducer from "./authReducer";
3 |
4 | export default combineReducers({
5 | auth: authReducer
6 | });
7 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/static/css/common.css:
--------------------------------------------------------------------------------
1 | nav {
2 | /* max-height: 60px; */
3 | background: linear-gradient(to right, rgba(45, 35, 35, 0.8) 0%, rgba(18, 3, 3, 0.67) 100%) !important;
4 |
5 | }
6 |
7 | .ov-nav-section {
8 | align-content: center;
9 | height: 60px;
10 | overflow: hidden;
11 | }
12 |
13 | .ov-color-white {
14 | color: white !important;
15 | }
16 |
17 | .ov-color-black {
18 | color: black !important;
19 | }
20 |
21 | .ov-nav-link:hover {
22 | background: rgb(45, 97, 194);
23 | height: 60px;
24 | }
25 |
26 | .ov-curser-pointer {
27 | cursor: pointer;
28 | }
29 |
--------------------------------------------------------------------------------
/src/static/css/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: 'Open Sans', sans-serif !important;
3 | }
4 |
5 | nav {
6 | height: 60px;
7 | }
8 |
9 | .full-container {
10 | min-height: 100vh;
11 | /* background: linear-gradient(to right, rgb(54, 20, 166) 0%, rgb(33, 162, 233) 100%); */
12 | background: linear-gradient(to right, rgb(232, 246, 244) 0%, rgb(237, 247, 252) 100%);
13 | }
14 |
15 | .ov-post-author-img {
16 | width: 50px;
17 | height: 50px;
18 | }
19 |
20 | @media screen and (max-width: 650px) {
21 | .ov-padding-remove{
22 | padding: 1px 5px 1px 5px !important;
23 | }
24 |
25 | .ov-profile-name{
26 | font-size: 15px !important;
27 | }
28 |
29 | .ov-profile-bio {
30 | font-size: 13px !important;
31 | }
32 |
33 | .ov-post-tag {
34 | font-size: 10px !important;
35 | }
36 |
37 | .uk-link-heading {
38 | font-size: 15px !important;
39 | }
40 |
41 | .uk-text-small, .uk-text-meta {
42 | font-size: 13px !important;
43 | }
44 |
45 | .uk-navbar-center {
46 | display: none
47 | }
48 |
49 | .ov-post-author-img {
50 | width: 40px !important;
51 | height: 40px !important;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/static/img/avtar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/src/static/img/avtar.jpg
--------------------------------------------------------------------------------
/src/static/img/index-no-spin.svg:
--------------------------------------------------------------------------------
1 |
2 | marina
3 | anchor,ship,cruise,navy,sailor,marina,map
4 | cc0
5 | k9i3hz
6 |
--------------------------------------------------------------------------------
/src/static/img/index.svg:
--------------------------------------------------------------------------------
1 |
2 | marina
3 | anchor,ship,cruise,navy,sailor,marina,map
4 | cc0
5 | k9i3hz
6 |
--------------------------------------------------------------------------------
/src/static/img/index2.svg:
--------------------------------------------------------------------------------
1 |
2 | marina
3 | anchor,ship,cruise,navy,sailor,marina,map
4 | cc0
5 | k9i3hz
6 |
--------------------------------------------------------------------------------
/src/static/img/index3.svg:
--------------------------------------------------------------------------------
1 |
2 | marina
3 | anchor,ship,cruise,navy,sailor,marina,map
4 | cc0
5 | k9i3hz
6 |
--------------------------------------------------------------------------------
/src/static/img/index4.svg:
--------------------------------------------------------------------------------
1 |
2 | marina
3 | anchor,ship,cruise,navy,sailor,marina,map
4 | cc0
5 | k9i3hz
6 |
--------------------------------------------------------------------------------
/src/static/img/no-feeds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/src/static/img/no-feeds.png
--------------------------------------------------------------------------------
/src/static/img/retro-hop.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mohitkyadav/cruzz-web/296708dbd135a57026b33a63836d2a19b43b1da1/src/static/img/retro-hop.jpg
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from './reducers';
4 |
5 | const initialState = {};
6 | const middleware = [thunk];
7 |
8 | const store = createStore(rootReducer, initialState, compose(applyMiddleware(...middleware)));
9 |
10 | export default store;
11 |
--------------------------------------------------------------------------------
/src/tests/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { withRouter } from 'react-router'
4 |
5 | import App from '../components/App';
6 | import ProfilePage from './../components/profile/ProfilePage';
7 | import UserProfile from './../components/profile/UserProfile';
8 | import PostDetails from '../components/feed/PostDetails';
9 | import PostFeed from './../components/feed/PostFeed';
10 | import SignIn from './../components/common/SignIn';
11 | import SignUp from './../components/common/SignUp';
12 | import Header from './../components/common/Header';
13 | import PageSuggestions from './../components/common/PageSuggestions';
14 | import PostOperations from './../components/common/PostOperations';
15 |
16 | it('renders without crashing', () => {
17 | const div = document.createElement('div');
18 | ReactDOM.render(withRouter(), div);
19 | ReactDOM.unmountComponentAtNode(div);
20 | });
21 |
22 | it('renders without crashing', () => {
23 | const div = document.createElement('div');
24 | ReactDOM.render(withRouter(), div);
25 | ReactDOM.unmountComponentAtNode(div);
26 | });
27 |
28 | it('renders without crashing', () => {
29 | const div = document.createElement('div');
30 | ReactDOM.render(withRouter(), div);
31 | ReactDOM.unmountComponentAtNode(div);
32 | });
33 |
34 | it('renders without crashing', () => {
35 | const div = document.createElement('div');
36 | ReactDOM.render(withRouter(), div);
37 | ReactDOM.unmountComponentAtNode(div);
38 | });
39 |
40 | it('renders without crashing', () => {
41 | const div = document.createElement('div');
42 | ReactDOM.render(withRouter(), div);
43 | ReactDOM.unmountComponentAtNode(div);
44 | });
45 |
46 | it('renders without crashing', () => {
47 | const div = document.createElement('div');
48 | ReactDOM.render(withRouter(), div);
49 | ReactDOM.unmountComponentAtNode(div);
50 | });
51 |
52 | it('renders without crashing', () => {
53 | const div = document.createElement('div');
54 | ReactDOM.render(withRouter(), div);
55 | ReactDOM.unmountComponentAtNode(div);
56 | });
57 |
58 | it('renders without crashing', () => {
59 | const div = document.createElement('div');
60 | ReactDOM.render(withRouter(), div);
61 | ReactDOM.unmountComponentAtNode(div);
62 | });
63 |
64 | it('renders without crashing', () => {
65 | const div = document.createElement('div');
66 | ReactDOM.render(withRouter(), div);
67 | ReactDOM.unmountComponentAtNode(div);
68 | });
69 |
70 | it('renders without crashing', () => {
71 | const div = document.createElement('div');
72 | ReactDOM.render(withRouter(), div);
73 | ReactDOM.unmountComponentAtNode(div);
74 | });
75 |
--------------------------------------------------------------------------------
/src/utils/setAuthToken.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const setAuthToken = token => {
4 | if (token) {
5 | // Apply to every request
6 | axios.defaults.headers.common['Authorization'] = token;
7 | } else {
8 | // Delete auth header
9 | delete axios.defaults.headers.common['Authorization'];
10 | }
11 | };
12 |
13 | export default setAuthToken;
14 |
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | export const isEmpty = value =>
2 | value === undefined ||
3 | value === null ||
4 | (typeof value === 'object' && Object.keys(value).length === 0) ||
5 | (typeof value === 'string' && value.trim().length === 0);
6 |
--------------------------------------------------------------------------------