├── .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 | feed page 37 | 38 | ### Profile Page 39 | 40 | feed page 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 | 2 | marina 3 | anchor,ship,cruise,navy,sailor,marina,map 4 | cc0 5 | k9i3hz 6 | -------------------------------------------------------------------------------- /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 |
62 | 63 |
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 | No Feed Img 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 | me 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 | logo 29 |
  • 30 |
31 | ); 32 | } else { 33 | spinner = ( 34 |
    35 |
  • 36 | logo 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 | me 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 |
101 |
102 |
103 | { 104 | this.state.operation === "new" ? 105 | ( 106 | New Post 107 | ): ( 108 | Edit Post 109 | ) 110 | } 111 | 112 | 113 |
114 | { 115 | this.state.operation === "edit" ? 116 | ( 117 | 118 | ): ( 119 | 120 | ) 121 | } 122 |
123 |

Title's character limit is 50

124 |
125 |
126 | 127 |
128 | { 129 | this.state.operation === "edit" ? 130 | ( 131 | 132 | ): ( 133 | 134 | ) 135 | } 136 |
137 |
138 | { 139 | this.state.operation === "edit" ? 140 | ( 141 | 142 | ): ( 143 | 144 | ) 145 | } 146 |
147 |
148 | 149 |
150 |
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 |
45 |
46 |
47 | 48 | 49 |
50 | 51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 |
59 | { 60 | this.props.auth.errors ? ( 61 | this.props.auth.errors.error[0] 62 | ): null 63 | } 64 |
65 |

66 | 67 |

68 |
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 |
70 |
71 |
72 | 73 | 74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 | 82 |
83 |
84 | 85 | 86 |
87 |
88 | 89 |
90 |
91 | 92 | 93 |
94 |
95 | 96 |
97 |
98 | 99 | 100 |
101 |
102 |
103 | 104 | {this.handlePassword(e)}} required={true}/> 105 |
106 |
107 | { 108 | this.props.auth.errors ? ( 109 |
110 |
111 | { 112 | this.props.auth.errors.email 113 | } 114 |
115 |
116 | { 117 | this.props.auth.errors.username 118 | } 119 |
120 | { 121 | this.props.auth.errors.password ? ( 122 |
123 | { 124 | "Password : " + this.props.auth.errors.password 125 | } 126 |
127 | ): null 128 | } 129 | 130 |
131 | ): null 132 | } 133 | { 134 | this.state.mailError !== '' ? ( 135 |
136 | { 137 | this.state.mailError 138 | } 139 |
140 | ): null 141 | } 142 | { 143 | this.state.passwordError !== '' ? ( 144 |
145 | { 146 | this.state.passwordError 147 | } 148 |
149 | ): null 150 | } 151 |

152 | { 153 | this.state.mailError !== '' ? ( 154 | 155 | ): ( 156 | 157 | ) 158 | } 159 |

160 |
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 |
126 | 127 |
128 |
129 | 130 | 131 |
132 |
133 |
134 | ) 135 | })} 136 |
137 | ):( 138 |
139 | loading 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 |
191 |
192 |
this.editComment(e, comment.id)}> 193 | 194 |

195 | hit enter to update your comment or click cancel to go back 196 |

197 |
198 | 199 | { 200 | this.props.auth.loading ? ( 201 |
202 | ) : () 203 | } 204 |
205 |
206 |
207 |
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 |
199 |
200 |
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 |
59 | 60 |
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 |
54 | 55 |
56 | ) 57 | })} 58 |
59 | ):( 60 |
61 |
62 | loading 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 |
186 | this.uploadProfilePic(e)}/> 187 |
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 |
214 |
215 | 216 | Update Profile 217 |
218 |

First Name

219 | 220 |
221 | 222 |
223 |

Last Name

224 | 225 |
226 | 227 |
228 |

E-mail

229 | 230 |
231 | 232 |
233 |

Bio

234 | 235 |
236 | 237 |
238 | 239 |
240 |

241 | 242 | 243 |

244 | { 245 | this.props.auth.loading ? ( 246 |
247 | ) : () 248 | } 249 |
250 |
251 | 252 |
253 |
254 | 255 |
256 | this.uploadCoverPic(e)}/> 257 |
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 | me 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 | me 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 |
381 | 382 |
383 |
384 | 385 |
386 |
387 | { 388 | this.state.posts.length > 0 ? ( 389 |
390 | {this.state.posts.map((post, key) => { 391 | return ( 392 |
393 | 394 |
395 | ) 396 | })} 397 |
398 | ):( 399 |
400 |
401 | loading 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 | me 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 | me 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 | (loading) 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 |
297 | 298 |
299 |
300 | 301 |
302 |
303 | { 304 | this.state.posts.length > 0 ? ( 305 |
306 | {this.state.posts.map((post, key) => { 307 | return ( 308 |
309 | 310 |
311 | ) 312 | })} 313 |
314 | ):( 315 | this.state.posts.length > 0 ? ( 316 |
317 |
318 | loading 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 | --------------------------------------------------------------------------------