├── src
├── stylesheets
│ ├── overides.scss
│ ├── variables.scss
│ ├── main.scss
│ └── mixins.scss
├── pages
│ ├── home-page
│ │ ├── home-page.scss
│ │ └── HomePage.js
│ ├── posts-page
│ │ ├── posts-page.scss
│ │ └── PostsPage.js
│ ├── signup-page
│ │ ├── signup-page.scss
│ │ └── SignUpPage.js
│ └── signin-page
│ │ ├── signin-page.scss
│ │ └── SigninPage.js
├── components
│ ├── signin-form
│ │ ├── signin-form.scss
│ │ └── SigninForm.js
│ ├── signup-form
│ │ ├── signup-form.scss
│ │ └── SignupForm.js
│ ├── navbar
│ │ ├── navbar.scss
│ │ └── Navbar.js
│ └── svg
│ │ └── Logo.js
├── reducers
│ ├── index.js
│ ├── posts-reducer.js
│ └── auth-reducer.js
├── actions
│ ├── posts-actions
│ │ ├── types.js
│ │ ├── service.js
│ │ └── actions.js
│ └── auth-actions
│ │ ├── types.js
│ │ ├── service.js
│ │ └── actions.js
├── stores
│ └── store-dev.js
├── config
│ └── axios-instance.js
├── index.js
└── App.js
├── .env.example
├── img
├── 1.png
├── 2.png
└── 3.png
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── .gitignore
├── package.json
└── README.md
/src/stylesheets/overides.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stylesheets/variables.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL=
2 |
--------------------------------------------------------------------------------
/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halberio/halber-react-kit/HEAD/img/1.png
--------------------------------------------------------------------------------
/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halberio/halber-react-kit/HEAD/img/2.png
--------------------------------------------------------------------------------
/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halberio/halber-react-kit/HEAD/img/3.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halberio/halber-react-kit/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halberio/halber-react-kit/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halberio/halber-react-kit/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/pages/home-page/home-page.scss:
--------------------------------------------------------------------------------
1 | @import "../../stylesheets/variables";
2 | @import "../../stylesheets/mixins";
3 |
4 | .home-page {
5 | .title {
6 | margin-top: 100px;
7 | text-align: center;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/signin-form/signin-form.scss:
--------------------------------------------------------------------------------
1 | .signin-form {
2 | width: 100%;
3 | max-width: 400px;
4 | display: block;
5 | margin: auto;
6 | .submit-button {
7 | margin: auto;
8 | display: block;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/signup-form/signup-form.scss:
--------------------------------------------------------------------------------
1 | .signup-form {
2 | width: 100%;
3 | max-width: 400px;
4 | display: block;
5 | margin: auto;
6 | .submit-button {
7 | margin: auto;
8 | display: block;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/stylesheets/main.scss:
--------------------------------------------------------------------------------
1 | @import "mixins";
2 | @import "variables";
3 | @import "overides";
4 |
5 | html {
6 | font-size: 12px;
7 | }
8 | body {
9 | padding: 0;
10 | margin: 0;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | }
14 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import authReducer from "./auth-reducer";
3 | import postsReducer from "./posts-reducer";
4 |
5 | const rootReducer = combineReducers({
6 | authReducer,
7 | postsReducer
8 | });
9 |
10 | export default rootReducer;
11 |
--------------------------------------------------------------------------------
/src/actions/posts-actions/types.js:
--------------------------------------------------------------------------------
1 | /*
2 | @
3 | This file contains the types constants
4 | @
5 | */
6 |
7 | export const FETCH_POSTS_REQUEST = "FETCH_POSTS_REQUEST ";
8 | export const FETCH_POSTS_SUCCESS = "FETCH_POSTS_SUCCESS ";
9 | export const FETCH_POSTS_FAILURE = "FETCH_POSTS_FAILURE ";
10 |
--------------------------------------------------------------------------------
/src/pages/posts-page/posts-page.scss:
--------------------------------------------------------------------------------
1 | @import "../../stylesheets/variables";
2 | @import "../../stylesheets/mixins";
3 |
4 | .posts-page {
5 | @include flexbox();
6 | @include flex-wrap(wrap);
7 | @include justify-content(center);
8 |
9 | .ant-card {
10 | margin: 10px;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/signup-page/signup-page.scss:
--------------------------------------------------------------------------------
1 | @import "../../stylesheets/mixins";
2 | .signup-page {
3 | .title {
4 | display: block;
5 | margin: 100px 0;
6 | text-align: center;
7 | }
8 | .content {
9 | @include flexbox();
10 | @include justify-content(center);
11 | @include align-items(center);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/actions/posts-actions/service.js:
--------------------------------------------------------------------------------
1 | /*
2 | @
3 | This file contains the requests ( services )
4 | @
5 | */
6 |
7 | import axios from "axios";
8 |
9 | function fetchPostsRequest() {
10 | return axios.get("https://jsonplaceholder.typicode.com/posts");
11 | }
12 |
13 | const PostsServices = {
14 | fetchPostsRequest
15 | };
16 |
17 | export default PostsServices;
18 |
--------------------------------------------------------------------------------
/src/pages/signin-page/signin-page.scss:
--------------------------------------------------------------------------------
1 | @import "../../stylesheets/mixins";
2 | @import "../../stylesheets/variables";
3 |
4 | .signin-page {
5 | .title {
6 | display: block;
7 | margin: 100px 0;
8 | text-align: center;
9 | }
10 | .content {
11 | @include flexbox();
12 | @include justify-content(center);
13 | @include align-items(center);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/pages/home-page/HomePage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./home-page.scss";
3 | import { Typography } from "antd";
4 |
5 | const HomePage = () => {
6 | return (
7 |
8 |
9 | Developed by Halber
10 |
11 |
12 | );
13 | };
14 |
15 | export default HomePage;
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 |
27 | .idea/
28 | /.idea
29 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/src/reducers/posts-reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | FETCH_POSTS_FAILURE,
3 | FETCH_POSTS_REQUEST,
4 | FETCH_POSTS_SUCCESS
5 | } from "../actions/posts-actions/types";
6 |
7 | const intialState = {
8 | posts: []
9 | };
10 |
11 | const postsReducer = (state = intialState, action) => {
12 | switch (action.type) {
13 | case FETCH_POSTS_REQUEST:
14 | return state;
15 | case FETCH_POSTS_SUCCESS:
16 | return { ...state, posts: action.payload };
17 |
18 | case FETCH_POSTS_FAILURE:
19 | return state;
20 |
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 | export default postsReducer;
27 |
--------------------------------------------------------------------------------
/src/stores/store-dev.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import { applyMiddleware } from "redux";
3 | import { createLogger } from "redux-logger";
4 | import reduxThunk from "redux-thunk";
5 | import rootReducer from "../reducers/index";
6 |
7 | const logger = createLogger({
8 | collapsed: true,
9 | duration: true,
10 | colors: {
11 | title: () => "#0294B5",
12 | prevState: () => "#7286E9",
13 | action: () => "#FF534D",
14 | nextState: () => "#1DB954",
15 | error: () => "#FF534D"
16 | }
17 | });
18 | const store = createStore(rootReducer, applyMiddleware(reduxThunk, logger));
19 |
20 | export default store;
21 |
--------------------------------------------------------------------------------
/src/pages/signup-page/SignUpPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import WrappedSignupForm from "../../components/signup-form/SignupForm";
4 | import { Typography } from "antd";
5 | import { signup } from "../../actions/auth-actions/actions";
6 | import "./signup-page.scss";
7 |
8 | const SignUpPage = props => {
9 | return (
10 |
11 |
Sign up page
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | export default connect(
20 | null,
21 | { signup }
22 | )(SignUpPage);
23 |
--------------------------------------------------------------------------------
/src/pages/signin-page/SigninPage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import WrappedSigninForm from "../../components/signin-form/SigninForm";
4 | import { Typography } from "antd";
5 | import { signin } from "../../actions/auth-actions/actions";
6 |
7 | import "./signin-page.scss";
8 |
9 | const SigninPage = props => {
10 | return (
11 |
12 |
Sign in page
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default connect(
21 | null,
22 | { signin }
23 | )(SigninPage);
24 |
--------------------------------------------------------------------------------
/src/actions/posts-actions/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | @
3 | This file contains the actions creators
4 | @
5 | */
6 |
7 | import {
8 | FETCH_POSTS_REQUEST,
9 | FETCH_POSTS_FAILURE,
10 | FETCH_POSTS_SUCCESS
11 | } from "./types";
12 |
13 | import PostsServices from "./service";
14 |
15 | export function fetchPosts() {
16 | return async dispatch => {
17 | dispatch({
18 | type: FETCH_POSTS_REQUEST
19 | });
20 | try {
21 | const response = await PostsServices.fetchPostsRequest();
22 |
23 | await dispatch({
24 | type: FETCH_POSTS_SUCCESS,
25 | payload: response.data
26 | });
27 | } catch (e) {
28 | dispatch({
29 | type: FETCH_POSTS_FAILURE
30 | });
31 | }
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/actions/auth-actions/types.js:
--------------------------------------------------------------------------------
1 | /*
2 | @
3 | This file contains the types constants
4 | @
5 | */
6 |
7 | export const SIGNIN_REQUEST = "SIGNIN_REQUEST ";
8 | export const SIGNIN_FAILURE = "SIGNIN_FAILURE ";
9 | export const SIGNIN_SUCCESS = "SIGNIN_SUCCESS ";
10 |
11 | export const SIGNUP_REQUEST = "SIGNUP_REQUEST ";
12 | export const SIGNUP_ERROR = "SIGNUP_ERROR ";
13 | export const SIGNUP_SUCCESS = "SIGNUP_SUCCESS ";
14 |
15 | export const GET_AUTH_REQUEST = "GET_AUTH_REQUEST ";
16 | export const GET_AUTH_SUCCESS = "GET_AUTH_SUCCESS ";
17 | export const GET_AUTH_FAILURE = "GET_AUTH_FAILURE ";
18 |
19 | export const LOGOUT_REQUEST = "LOGOUT_REQUEST ";
20 | export const LOGOUT_FAILURE = "LOGOUT_FAILURE ";
21 | export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS ";
22 |
23 | export const DISCONNECT_THE_USER = "DISCONNECT_THE_USER ";
24 | export const CONNECT_THE_USER = "CONNECT_THE_USER ";
25 |
--------------------------------------------------------------------------------
/src/pages/posts-page/PostsPage.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { connect } from "react-redux";
3 | import { fetchPosts } from "../../actions/posts-actions/actions";
4 | import { Card } from "antd";
5 |
6 | import "./posts-page.scss";
7 |
8 | const PostsPage = props => {
9 | useEffect(() => {
10 | props.fetchPosts();
11 | }, []);
12 | return (
13 |
14 | {props.posts.map((ele, index) => {
15 | return (
16 |
17 | {ele.body}
18 |
19 | );
20 | })}
21 |
22 | );
23 | };
24 |
25 | const mapStateToProps = reduxStore => {
26 | return {
27 | posts: reduxStore.postsReducer.posts
28 | };
29 | };
30 |
31 | export default connect(
32 | mapStateToProps,
33 | { fetchPosts }
34 | )(PostsPage);
35 |
--------------------------------------------------------------------------------
/src/actions/auth-actions/service.js:
--------------------------------------------------------------------------------
1 | /*
2 | @
3 | This file contains the requests ( services )
4 | @
5 | */
6 |
7 | import axiosInstance from "../../config/axios-instance";
8 |
9 | function logoutRequest() {
10 | return axiosInstance({
11 | method: "get",
12 | url: "auth/logout",
13 | data: null
14 | });
15 | }
16 |
17 | function signinRequest(body) {
18 | return axiosInstance({
19 | method: "post",
20 | url: "auth/signin",
21 | data: body
22 | });
23 | }
24 |
25 | function signupRequest(body) {
26 | return axiosInstance({
27 | method: "post",
28 | url: "auth/signup",
29 | data: body
30 | });
31 | }
32 |
33 | function getAuthUserRequest() {
34 | return axiosInstance({
35 | method: "get",
36 | url: "auth/user"
37 | });
38 | }
39 |
40 | const AuthServices = {
41 | signinRequest,
42 | signupRequest,
43 | logoutRequest,
44 | getAuthUserRequest
45 | };
46 |
47 | export default AuthServices;
48 |
--------------------------------------------------------------------------------
/src/components/navbar/navbar.scss:
--------------------------------------------------------------------------------
1 | @import "../../stylesheets/variables";
2 | @import "../../stylesheets/mixins";
3 |
4 | .navbar {
5 | @include flexbox();
6 | @include align-items(center);
7 | .first-element {
8 | margin-left: 40px;
9 | }
10 | .navbar-brand,
11 | .navbar-avatar {
12 | @include flexbox();
13 | @include align-items(center);
14 | position: relative;
15 | &.ant-menu-item-selected,
16 | &.ant-menu-item-active {
17 | color: transparent !important;
18 | border-bottom: 0 !important;
19 | }
20 | }
21 | .navbar-brand {
22 | .brand-link {
23 | position: relative;
24 | svg {
25 | position: absolute;
26 | top: 0;
27 | bottom: 0;
28 | margin: auto;
29 | height: 30px;
30 | width: 30px;
31 | }
32 | }
33 | }
34 | .navbar-right {
35 | margin-left: auto;
36 | }
37 | .avatar {
38 | i {
39 | margin: 0;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "halber-react-kit",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^3.23.4",
7 | "axios": "^0.19.0",
8 | "history": "^4.10.1",
9 | "node-sass": "^4.12.0",
10 | "react": "^16.9.0",
11 | "react-dom": "^16.9.0",
12 | "react-redux": "^7.1.1",
13 | "react-router-dom": "^5.0.1",
14 | "react-scripts": "3.1.2",
15 | "redux": "^4.0.4",
16 | "redux-thunk": "^2.3.0"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "devDependencies": {
40 | "husky": "^3.0.5",
41 | "prettier": "1.18.2",
42 | "pretty-quick": "^1.11.1",
43 | "redux-logger": "^3.0.6"
44 | },
45 | "husky": {
46 | "hooks": {
47 | "pre-commit": "pretty-quick --staged"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/config/axios-instance.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import store from "../stores/store-dev";
3 | import { logout } from "../actions/auth-actions/actions";
4 |
5 | import { history } from "../index";
6 |
7 | const token = localStorage.getItem("halber_token");
8 |
9 | const axiosInstance = axios.create({
10 | baseURL: process.env.REACT_APP_API_URL,
11 | headers: {
12 | Accept: "application/json",
13 | "Content-Type": "application/json",
14 | Authorization: `Bearer ${token}`
15 | }
16 | });
17 |
18 | axiosInstance.interceptors.request.use(
19 | function(config) {
20 | // Do something before request is sent
21 | return config;
22 | },
23 | function(error) {
24 | // Do something with request error
25 | return Promise.reject(error);
26 | }
27 | );
28 |
29 | // Add a response interceptor
30 | axiosInstance.interceptors.response.use(
31 | function(response) {
32 | // Do something with response data
33 | return response;
34 | },
35 | function(error) {
36 | switch (error.response.status) {
37 | case 401:
38 | // unauthorized -> token is invalid or expired
39 | // User must reconnect!
40 | store.dispatch(logout());
41 | history.push("/login");
42 | break;
43 | default:
44 | break;
45 | }
46 | // Do something with response error
47 | return Promise.reject(error);
48 | }
49 | );
50 |
51 | export default axiosInstance;
52 |
--------------------------------------------------------------------------------
/src/components/signin-form/SigninForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Icon, Input, Button } from "antd";
3 |
4 | import "./signin-form.scss";
5 |
6 | const SigninForm = props => {
7 | const { getFieldDecorator } = props.form;
8 |
9 | const handleSubmit = e => {
10 | e.preventDefault();
11 | props.form.validateFields((err, data) => {
12 | if (!err) {
13 | props.signin(data);
14 | }
15 | });
16 | };
17 |
18 | return (
19 |
21 | {getFieldDecorator("email", {
22 | rules: [{ required: true, message: "Please input your email!" }]
23 | })(
24 | }
26 | placeholder="Email"
27 | />
28 | )}
29 |
30 |
31 | {getFieldDecorator("password", {
32 | rules: [{ required: true, message: "Please input your Password!" }]
33 | })(
34 | }
36 | type="password"
37 | placeholder="Password"
38 | />
39 | )}
40 |
41 |
42 |
45 |
46 |
47 | );
48 | };
49 |
50 | const WrappedSigninForm = Form.create({ name: "normal_login" })(SigninForm);
51 |
52 | export default WrappedSigninForm;
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## **Halber-react-kit**
2 |
3 | a ready to use starter kit with authentication based on token.
4 |
5 | **NB:**
6 | Do not forget to rename the .env.example file to .env and put there your REST API url
7 |
8 | ## **Packages installed** and ready to use:
9 |
10 | - Ant design
11 | - Redux
12 | - Axios ( with config & interceptors )
13 | - SCSS ( with a lot of MIXINS )
14 |
15 | ## **Project structure:**
16 |
17 | - actions
18 | - components
19 | - config
20 | - pages
21 | - reducers
22 | - stores
23 | - stylesheets
24 | - tools
25 | - App.js
26 | - index.js
27 |
28 | ## **Useful informations**
29 |
30 | - In the config folder you find a file named api.js, here you need to add your Restful API url
31 | - Also in config folder you can find the used axios instance for all requests so you can do things before or after sending & getting requests or responses
32 | - I recommend to keep this structure of the project so you can work with a lot of conventions
33 | - My Restful API is made with Laravel passport
34 | - We are also trying to keep this project up to date by adding new features and using latest versions of packages
35 | - For any other information or question don't hesitate to contact us.
36 |
37 | #
38 |
39 | Email : **hello@halber.io**
40 |
41 | Website : **https://halber.io**
42 |
43 | #
44 |
45 | **Sign in page:**
46 | 
47 |
48 | #
49 |
50 | **Sign up page:**
51 | 
52 |
53 | #
54 |
55 | **Articles page:**
56 | 
57 |
58 | #
59 |
--------------------------------------------------------------------------------
/src/components/svg/Logo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Logo = () => {
4 | return (
5 |
57 | );
58 | };
59 |
60 | export default Logo;
61 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/navbar/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Avatar, Dropdown, Menu } from "antd";
3 | import { NavLink } from "react-router-dom";
4 | import "./navbar.scss";
5 | import Logo from "../svg/Logo";
6 |
7 | const Navbar = props => {
8 | return (
9 | <>
10 |
48 | >
49 | );
50 | };
51 | export default Navbar;
52 |
53 | const AvatarDropdown = props => {
54 | return (
55 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import ReactDOM from "react-dom";
3 | import { Router } from "react-router-dom";
4 | import { Provider, connect } from "react-redux";
5 | import { createBrowserHistory } from "history";
6 | import axiosInstance from "./config/axios-instance";
7 | import store from "./stores/store-dev";
8 |
9 | import App from "./App";
10 |
11 | import "antd/dist/antd.min.css";
12 | import "./stylesheets/main.scss";
13 |
14 | import { connectTheUser, getAuthUser } from "./actions/auth-actions/actions";
15 |
16 | export const history = createBrowserHistory();
17 |
18 | const token = localStorage.getItem("halber_token");
19 |
20 | if (token) {
21 | // if token exists in local storage!
22 | store.dispatch(connectTheUser(token));
23 | }
24 |
25 | store.subscribe(() => {
26 | const reduxSubs = store.getState();
27 | if (reduxSubs.authReducer.token) {
28 | axiosInstance.defaults.headers.common[
29 | "Authorization"
30 | ] = `Bearer ${reduxSubs.authReducer.token}`;
31 | axiosInstance.defaults.headers[
32 | "Authorization"
33 | ] = `Bearer ${reduxSubs.authReducer.token}`;
34 | }
35 | });
36 |
37 | const WrappedApp = props => {
38 | useEffect(() => {
39 | if (token) {
40 | // We need to check if the token are valid or not by getting the auth user
41 | props.store.dispatch(getAuthUser());
42 | }
43 | }, []);
44 |
45 | return (
46 | <>
47 | {/*if token is available we try to get the user once each time the app gets reloaded, so we don't need to
48 | fetch the auth user everytime we need him,*/}
49 |
50 | {token && props.isLoadingUser ? Loading...
: props.children}
51 | >
52 | );
53 | };
54 |
55 | const mapStateToProps = reduxStore => {
56 | return {
57 | isLoadingUser: reduxStore.authReducer.isLoadingUser
58 | };
59 | };
60 |
61 | const ConnectedWrappedApp = connect(mapStateToProps)(WrappedApp);
62 |
63 | ReactDOM.render(
64 |
65 |
66 |
67 |
68 |
69 |
70 | ,
71 | document.getElementById("root")
72 | );
73 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import { Route, Redirect, Switch, withRouter } from "react-router-dom";
4 | import HomePage from "./pages/home-page/HomePage";
5 | import Navbar from "./components/navbar/Navbar";
6 | import SigninPage from "./pages/signin-page/SigninPage";
7 |
8 | import SignUpPage from "./pages/signup-page/SignUpPage";
9 | import { logout } from "./actions/auth-actions/actions";
10 | import PostsPage from "./pages/posts-page/PostsPage";
11 |
12 | const App = props => {
13 | return (
14 |
15 |
20 |
21 |
22 |
27 |
32 |
37 | />
38 |
39 |
40 | );
41 | };
42 | function AuthRoute({ component: Component, authenticated, ...rest }) {
43 | return (
44 |
48 | authenticated ? (
49 |
50 | ) : (
51 |
54 | )
55 | }
56 | />
57 | );
58 | }
59 |
60 | function GuestRoute({ component: Component, authenticated, ...rest }) {
61 | return (
62 |
66 | !authenticated ? :
67 | }
68 | />
69 | );
70 | }
71 |
72 | const mapStateToProps = reduxStore => {
73 | return {
74 | isLoggedIn: reduxStore.authReducer.isLoggedIn,
75 | user: reduxStore.authReducer.user,
76 | isLoadingUser: reduxStore.authReducer.isLoadingUser
77 | };
78 | };
79 |
80 | export default withRouter(
81 | connect(
82 | mapStateToProps,
83 | { logout }
84 | )(App)
85 | );
86 |
--------------------------------------------------------------------------------
/src/components/signup-form/SignupForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Form, Icon, Input, Button } from "antd";
3 |
4 | import "./signup-form.scss";
5 |
6 | const SignupForm = props => {
7 | const { getFieldDecorator } = props.form;
8 | const handleSubmit = e => {
9 | e.preventDefault();
10 | props.form.validateFields((err, data) => {
11 | if (!err) {
12 | props.signup(data);
13 | }
14 | });
15 | };
16 | return (
17 |
19 | {getFieldDecorator("name", {
20 | rules: [{ required: true, message: "Please input your name!" }]
21 | })(
22 | }
24 | placeholder="Name"
25 | />
26 | )}
27 |
28 |
29 |
30 | {getFieldDecorator("email", {
31 | rules: [{ required: true, message: "Please input your email!" }]
32 | })(
33 | }
35 | placeholder="Email"
36 | />
37 | )}
38 |
39 |
40 | {getFieldDecorator("password", {
41 | rules: [{ required: true, message: "Please input your Password!" }]
42 | })(
43 | }
45 | type="password"
46 | placeholder="Password"
47 | />
48 | )}
49 |
50 |
51 | {getFieldDecorator("password_confirmation", {
52 | rules: [
53 | {
54 | required: true,
55 | message: "Please input your Password confirmation!"
56 | }
57 | ]
58 | })(
59 | }
61 | type="password"
62 | placeholder="Password confirmation"
63 | />
64 | )}
65 |
66 |
67 |
70 |
71 |
72 | );
73 | };
74 |
75 | const WrappedSignupForm = Form.create({ name: "normal_login" })(SignupForm);
76 |
77 | export default WrappedSignupForm;
78 |
--------------------------------------------------------------------------------
/src/actions/auth-actions/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | @
3 | This file contains the actions creators
4 | @
5 | */
6 |
7 | import {
8 | SIGNIN_FAILURE,
9 | SIGNIN_REQUEST,
10 | SIGNIN_SUCCESS,
11 | SIGNUP_REQUEST,
12 | SIGNUP_SUCCESS,
13 | SIGNUP_ERROR,
14 | GET_AUTH_SUCCESS,
15 | GET_AUTH_FAILURE,
16 | GET_AUTH_REQUEST,
17 | LOGOUT_REQUEST,
18 | CONNECT_THE_USER,
19 | LOGOUT_SUCCESS,
20 | LOGOUT_FAILURE
21 | } from "./types";
22 |
23 | import AuthServices from "./service";
24 |
25 | export function getAuthUser() {
26 | return async dispatch => {
27 | await dispatch({
28 | type: GET_AUTH_REQUEST
29 | });
30 | try {
31 | const response = await AuthServices.getAuthUserRequest();
32 | await dispatch({
33 | type: GET_AUTH_SUCCESS,
34 | payload: {
35 | user: response.data,
36 | isLoggedIn: true
37 | }
38 | });
39 | } catch (e) {
40 | dispatch({
41 | type: GET_AUTH_FAILURE
42 | });
43 | }
44 | };
45 | }
46 |
47 | export function signin(values) {
48 | return async dispatch => {
49 | dispatch({ type: SIGNIN_REQUEST });
50 | try {
51 | const response = await AuthServices.signinRequest(values);
52 | dispatch({ type: SIGNIN_SUCCESS, payload: response.data });
53 | localStorage.setItem("halber_token", response.data.access_token);
54 | } catch (e) {
55 | dispatch({ type: SIGNIN_FAILURE });
56 | }
57 | };
58 | }
59 |
60 | export function signup(body) {
61 | return async dispatch => {
62 | dispatch({ type: SIGNUP_REQUEST });
63 | try {
64 | const response = await AuthServices.signupRequest(body);
65 | dispatch({ type: SIGNUP_SUCCESS, payload: response.data });
66 | localStorage.setItem("halber_token", response.data.access_token);
67 | } catch (e) {
68 | dispatch({ type: SIGNUP_ERROR });
69 | }
70 | };
71 | }
72 |
73 | export function logout() {
74 | return async dispatch => {
75 | dispatch({ type: LOGOUT_REQUEST });
76 | try {
77 | await AuthServices.logoutRequest();
78 | localStorage.removeItem("halber_token");
79 | dispatch({ type: LOGOUT_SUCCESS });
80 | } catch (e) {
81 | dispatch({ type: LOGOUT_FAILURE });
82 | }
83 | };
84 | }
85 |
86 | export function connectTheUser(token) {
87 | return async dispatch => {
88 | localStorage.setItem("halber_token", token);
89 | dispatch({
90 | type: CONNECT_THE_USER,
91 | payload: {
92 | token: token
93 | }
94 | });
95 | };
96 | }
97 |
--------------------------------------------------------------------------------
/src/reducers/auth-reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | SIGNIN_FAILURE,
3 | SIGNIN_REQUEST,
4 | SIGNIN_SUCCESS,
5 | SIGNUP_REQUEST,
6 | SIGNUP_SUCCESS,
7 | SIGNUP_ERROR,
8 | LOGOUT_REQUEST,
9 | DISCONNECT_THE_USER,
10 | CONNECT_THE_USER,
11 | GET_AUTH_REQUEST,
12 | GET_AUTH_SUCCESS,
13 | GET_AUTH_FAILURE,
14 | LOGOUT_FAILURE,
15 | LOGOUT_SUCCESS
16 | } from "../actions/auth-actions/types";
17 |
18 | const intialState = {
19 | user: null,
20 | isLoggedIn: false,
21 | isLoadingUser: true,
22 | token: null
23 | };
24 |
25 | const authReducer = (state = intialState, action) => {
26 | switch (action.type) {
27 | case GET_AUTH_REQUEST:
28 | return {
29 | ...state,
30 | isLoadingUser: true
31 | };
32 | case GET_AUTH_SUCCESS:
33 | return {
34 | ...state,
35 | user: action.payload.user,
36 | isLoadingUser: false
37 | };
38 |
39 | case GET_AUTH_FAILURE:
40 | return {
41 | ...state,
42 | isLoadingUser: false,
43 | isLoggedIn: false
44 | };
45 |
46 | // Sign in
47 | case SIGNIN_REQUEST:
48 | return state;
49 |
50 | case SIGNIN_SUCCESS:
51 | return {
52 | ...state,
53 | user: action.payload.user,
54 | isLoggedIn: true,
55 | token: action.payload.access_token
56 | };
57 | case SIGNIN_FAILURE:
58 | return state;
59 |
60 | //Sign up
61 | case SIGNUP_REQUEST:
62 | return state;
63 | case SIGNUP_SUCCESS:
64 | return {
65 | ...state,
66 | user: action.payload.user,
67 | isLoggedIn: true,
68 | token: action.payload.access_token
69 | };
70 | case SIGNUP_ERROR:
71 | return state;
72 |
73 | // Logout
74 |
75 | case LOGOUT_REQUEST:
76 | return state;
77 |
78 | case LOGOUT_SUCCESS:
79 | return {
80 | ...state,
81 | isLoggedIn: false,
82 | token: null
83 | };
84 | case LOGOUT_FAILURE:
85 | return state;
86 |
87 | // Connect & disconnect user ( no interaction with the server )
88 | case DISCONNECT_THE_USER:
89 | return {
90 | ...state,
91 | user: null,
92 | isLoggedIn: false,
93 | token: null
94 | };
95 |
96 | case CONNECT_THE_USER:
97 | return {
98 | ...state,
99 | isLoggedIn: true,
100 | token: action.payload.token // getting token from local storage
101 | };
102 | default:
103 | return state;
104 | }
105 | };
106 |
107 | export default authReducer;
108 |
--------------------------------------------------------------------------------
/src/stylesheets/mixins.scss:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------
2 | // Flexbox SASS mixins
3 | // The spec: http://www.w3.org/TR/css3-flexbox
4 | // --------------------------------------------------
5 |
6 | // Flexbox display
7 | @mixin flexbox() {
8 | display: -webkit-box;
9 | display: -moz-box;
10 | display: -ms-flexbox;
11 | display: -webkit-flex;
12 | display: flex;
13 | }
14 | @mixin filter-gradient($direction, $color-stops...) {
15 | // Direction has been omitted and happens to be a color-stop
16 | @if is-direction($direction) == false {
17 | $color-stops: $direction, $color-stops;
18 | $direction: 180deg;
19 | }
20 |
21 | background: nth(nth($color-stops, 1), 1);
22 | background: -webkit-linear-gradient(
23 | legacy-direction($direction),
24 | $color-stops
25 | );
26 | background: linear-gradient($direction, $color-stops);
27 | }
28 | // The 'flex' shorthand
29 | // - applies to: flex items
30 | // , initial, auto, or none
31 | @mixin flex($values) {
32 | -webkit-box-flex: $values;
33 | -moz-box-flex: $values;
34 | -webkit-flex: $values;
35 | -ms-flex: $values;
36 | flex: $values;
37 | }
38 |
39 | // Flex Flow Direction
40 | // - applies to: flex containers
41 | // row | row-reverse | column | column-reverse
42 | @mixin flex-direction($direction) {
43 | -webkit-flex-direction: $direction;
44 | -moz-flex-direction: $direction;
45 | -ms-flex-direction: $direction;
46 | flex-direction: $direction;
47 | }
48 |
49 | // Flex Line Wrapping
50 | // - applies to: flex containers
51 | // nowrap | wrap | wrap-reverse
52 | @mixin flex-wrap($wrap) {
53 | -webkit-flex-wrap: $wrap;
54 | -moz-flex-wrap: $wrap;
55 | -ms-flex-wrap: $wrap;
56 | flex-wrap: $wrap;
57 | }
58 |
59 | // Flex Direction and Wrap
60 | // - applies to: flex containers
61 | // ||
62 | @mixin flex-flow($flow) {
63 | -webkit-flex-flow: $flow;
64 | -moz-flex-flow: $flow;
65 | -ms-flex-flow: $flow;
66 | flex-flow: $flow;
67 | }
68 |
69 | // Display Order
70 | // - applies to: flex items
71 | //
72 | @mixin order($val) {
73 | -webkit-box-ordinal-group: $val;
74 | -moz-box-ordinal-group: $val;
75 | -ms-flex-order: $val;
76 | -webkit-order: $val;
77 | order: $val;
78 | }
79 |
80 | // Flex grow factor
81 | // - applies to: flex items
82 | //
83 | @mixin flex-grow($grow) {
84 | -webkit-flex-grow: $grow;
85 | -moz-flex-grow: $grow;
86 | -ms-flex-grow: $grow;
87 | flex-grow: $grow;
88 | }
89 |
90 | // Flex shrink
91 | // - applies to: flex item shrink factor
92 | //
93 | @mixin flex-shrink($shrink) {
94 | -webkit-flex-shrink: $shrink;
95 | -moz-flex-shrink: $shrink;
96 | -ms-flex-shrink: $shrink;
97 | flex-shrink: $shrink;
98 | }
99 |
100 | // Flex basis
101 | // - the initial main size of the flex item
102 | // - applies to: flex itemsnitial main size of the flex item
103 | //
104 | @mixin flex-basis($width) {
105 | -webkit-flex-basis: $width;
106 | -moz-flex-basis: $width;
107 | -ms-flex-basis: $width;
108 | flex-basis: $width;
109 | }
110 |
111 | // Axis Alignment
112 | // - applies to: flex containers
113 | // flex-start | flex-end | center | space-between | space-around
114 | @mixin justify-content($justify) {
115 | -webkit-justify-content: $justify;
116 | -moz-justify-content: $justify;
117 | -ms-justify-content: $justify;
118 | justify-content: $justify;
119 | -ms-flex-pack: $justify;
120 | }
121 |
122 | // Packing Flex Lines
123 | // - applies to: multi-line flex containers
124 | // flex-start | flex-end | center | space-between | space-around | stretch
125 | @mixin align-content($align) {
126 | -webkit-align-content: $align;
127 | -moz-align-content: $align;
128 | -ms-align-content: $align;
129 | align-content: $align;
130 | }
131 |
132 | // Cross-axis Alignment
133 | // - applies to: flex containers
134 | // flex-start | flex-end | center | baseline | stretch
135 | @mixin align-items($align) {
136 | -webkit-align-items: $align;
137 | -moz-align-items: $align;
138 | -ms-align-items: $align;
139 | -ms-flex-align: $align;
140 | align-items: $align;
141 | }
142 |
143 | // Cross-axis Alignment
144 | // - applies to: flex items
145 | // auto | flex-start | flex-end | center | baseline | stretch
146 | @mixin align-self($align) {
147 | -webkit-align-self: $align;
148 | -moz-align-self: $align;
149 | -ms-align-self: $align;
150 | align-self: $align;
151 | }
152 |
153 | @mixin box-shadow($left, $top, $radius, $color) {
154 | box-shadow: $left $top $radius $color;
155 | -webkit-box-shadow: $left $top $radius $color;
156 | -moz-box-shadow: $left $top $radius $color;
157 | }
158 |
159 | @mixin transition($property, $duration, $easing: linear) {
160 | transition: $property $duration $easing;
161 | -webkit-transition: $property $duration $easing;
162 | -moz-transition: $property $duration $easing;
163 | }
164 |
165 | @mixin border-radius($radius) {
166 | border-radius: $radius;
167 | -webkit-border-radius: $radius;
168 | -moz-border-radius: $radius;
169 | }
170 |
171 | @mixin border-radii($topleft, $topright, $bottomright, $bottomleft) {
172 | border-top-left-radius: $topleft;
173 | border-top-right-radius: $topright;
174 | border-bottom-right-radius: $bottomright;
175 | border-bottom-left-radius: $bottomleft;
176 | -webkit-border-top-left-radius: $topleft;
177 | -webkit-border-top-right-radius: $topright;
178 | -webkit-border-bottom-right-radius: $bottomright;
179 | -webkit-border-bottom-left-radius: $bottomleft;
180 | -moz-border-radius-topleft: $topleft;
181 | -moz-border-radius-topright: $topright;
182 | -moz-border-radius-bottomright: $bottomright;
183 | -moz-border-radius-bottomleft: $bottomleft;
184 | }
185 |
186 | @mixin gradient($color1, $color2) {
187 | background-color: $color1;
188 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=#{$color1}, endColorstr=#{$color2});
189 | background-image: -moz-linear-gradient(center top, $color1, $color2);
190 | background-image: -webkit-gradient(
191 | linear,
192 | 0% 0%,
193 | 0% 100%,
194 | from($color1),
195 | to($color2)
196 | );
197 | }
198 |
199 | // Browser Prefixes
200 | @mixin transform($transforms) {
201 | -webkit-transform: $transforms;
202 | -moz-transform: $transforms;
203 | -ms-transform: $transforms;
204 | transform: $transforms;
205 | }
206 |
207 | // Rotate
208 | @mixin rotate($deg) {
209 | @include transform(rotate(#{$deg}deg));
210 | }
211 |
212 | // Scale
213 | @mixin scale($scale) {
214 | @include transform(scale($scale));
215 | }
216 |
217 | // Translate
218 | @mixin translate($x, $y) {
219 | @include transform(translate($x, $y));
220 | }
221 |
222 | // Skew
223 | @mixin skew($x, $y) {
224 | @include transform(skew(#{$x}deg, #{$y}deg));
225 | }
226 |
227 | // Transform Origin
228 | @mixin transform-origin($origin) {
229 | -webkit-transform-origin: $origin;
230 | -moz-transform-origin: $origin;
231 | -ms-transform-origin: $origin;
232 | transform-origin: $origin;
233 | }
234 |
--------------------------------------------------------------------------------