mirror of
https://github.com/rjNemo/devbook_ts
synced 2026-06-12 05:26:46 +00:00
Signin (#6)
* install redux and set authSlice * connect navBar to the sotre * create User type * install react-redux-firebase * bind to firebase * connect App to firebase auth; display splash screen while loading auth state * install firestore * install firestore * enable interactive form * signup page functional * fix navbar bug * extract useForm hook * extract Alert component * sign in page functional * commenting * log out function * add private route
This commit is contained in:
parent
7333e3474b
commit
07dd7c5624
13 changed files with 395 additions and 618 deletions
|
|
@ -24,6 +24,7 @@
|
||||||
"moment": "^2.25.3",
|
"moment": "^2.25.3",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
|
"react-google-button": "^0.7.1",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.0",
|
||||||
"react-redux-firebase": "^3.4.0",
|
"react-redux-firebase": "^3.4.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
|
|
||||||
15
src/components/Alert.tsx
Normal file
15
src/components/Alert.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React, {FC} from 'react';
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
|
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Alert: FC<IProps> = ({text}) => (
|
||||||
|
<div className="alert alert-danger">
|
||||||
|
<FontAwesomeIcon icon={faExclamationTriangle} /> {text}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Alert;
|
||||||
|
|
@ -3,14 +3,17 @@ import React, {FC} from 'react';
|
||||||
import {Link} from 'react-router-dom';
|
import {Link} from 'react-router-dom';
|
||||||
import * as ROUTES from '../constants/routes';
|
import * as ROUTES from '../constants/routes';
|
||||||
//Redux
|
//Redux
|
||||||
|
import {compose} from '@reduxjs/toolkit';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
import {withFirebase, WithFirebaseProps} from 'react-redux-firebase';
|
||||||
import {selectProfile} from '../store/firebase';
|
import {selectProfile} from '../store/firebase';
|
||||||
// import {selectAuthState} from '../store/auth';
|
|
||||||
// Style
|
// Style
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||||
import {faCode, faSignOutAlt, faUser} from '@fortawesome/free-solid-svg-icons';
|
import {faCode, faSignOutAlt, faUser} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
// Typing
|
||||||
|
import User from '../models/User';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps extends WithFirebaseProps<User> {
|
||||||
isEmpty: boolean;
|
isEmpty: boolean;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +21,7 @@ interface IProps {
|
||||||
/**
|
/**
|
||||||
* Main Navbar serves navigation routes.
|
* Main Navbar serves navigation routes.
|
||||||
*/
|
*/
|
||||||
const NavBar: FC<IProps> = ({isEmpty, isLoaded}) => {
|
const NavBar: FC<IProps> = ({firebase, isEmpty, isLoaded}) => {
|
||||||
const publicLinks = (
|
const publicLinks = (
|
||||||
<ul data-testid="publicLinks">
|
<ul data-testid="publicLinks">
|
||||||
<li>
|
<li>
|
||||||
|
|
@ -58,7 +61,11 @@ const NavBar: FC<IProps> = ({isEmpty, isLoaded}) => {
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to={ROUTES.SIGN_IN} data-testid="logoutLink">
|
<Link
|
||||||
|
to={ROUTES.SIGN_IN}
|
||||||
|
data-testid="logoutLink"
|
||||||
|
onClick={() => firebase.logout()}
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={faSignOutAlt} />
|
<FontAwesomeIcon icon={faSignOutAlt} />
|
||||||
<span className="hide-sm"> Log out</span>
|
<span className="hide-sm"> Log out</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -67,7 +74,7 @@ const NavBar: FC<IProps> = ({isEmpty, isLoaded}) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Display appropriated links after loading given authenticated prop */
|
/** Display appropriated links after loading given authenticated prop */
|
||||||
const RenderLinks = !isLoaded && !isEmpty ? privateLinks : publicLinks;
|
const RenderLinks = isLoaded && !isEmpty ? privateLinks : publicLinks;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar bg-dark">
|
<nav className="navbar bg-dark">
|
||||||
|
|
@ -82,5 +89,6 @@ const NavBar: FC<IProps> = ({isEmpty, isLoaded}) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** connect HOC subscribes to the store */
|
/** connect HOC subscribes to the store */
|
||||||
export default connect(selectProfile)(NavBar);
|
|
||||||
//NavBar;
|
const enhance = compose<FC>(connect(selectProfile), withFirebase);
|
||||||
|
export default enhance(NavBar);
|
||||||
|
|
|
||||||
28
src/hooks/index.ts
Normal file
28
src/hooks/index.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {useState, ChangeEvent} from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provide onChange handler and reset function
|
||||||
|
* T is the initFormData object type
|
||||||
|
*
|
||||||
|
* @param initFormData initial state of the form
|
||||||
|
* @returns formData object,
|
||||||
|
* @returns handleChange function to pass to input tag
|
||||||
|
* @returns resetForm function to revert to initFormData
|
||||||
|
* */
|
||||||
|
const useForm = <T,>(initFormData: T) => {
|
||||||
|
const [formData, setFormData] = useState<T>(initFormData);
|
||||||
|
|
||||||
|
/** update each input state value onChange */
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>): void =>
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** clean form after successful submition */
|
||||||
|
const resetForm = () => setFormData(initFormData);
|
||||||
|
|
||||||
|
return {formData, handleChange, resetForm};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useForm;
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
interface User {
|
interface User {
|
||||||
name: string;
|
displayName: string;
|
||||||
email: string;
|
email: string;
|
||||||
picture: string;
|
avatarUrl: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** User constructor */
|
||||||
|
export const newUser = (
|
||||||
|
displayName: string,
|
||||||
|
email: string,
|
||||||
|
avatarUrl: string = '',
|
||||||
|
): User => ({
|
||||||
|
displayName,
|
||||||
|
email,
|
||||||
|
avatarUrl,
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,112 @@
|
||||||
import React, {FC} from 'react';
|
import React, {FC, useState} from 'react';
|
||||||
|
// Redux
|
||||||
|
import {compose} from '@reduxjs/toolkit';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {WithFirebaseProps, withFirebase} from 'react-redux-firebase';
|
||||||
|
import {selectProfile} from '../store/firebase';
|
||||||
|
// Routing
|
||||||
|
import {Link, Redirect} from 'react-router-dom';
|
||||||
import * as ROUTES from '../constants/routes';
|
import * as ROUTES from '../constants/routes';
|
||||||
|
// Style
|
||||||
|
import GoogleButton from 'react-google-button';
|
||||||
import Header from '../components/Header';
|
import Header from '../components/Header';
|
||||||
import {Link} from 'react-router-dom';
|
import Alert from '../components/Alert';
|
||||||
|
// Typing
|
||||||
|
import User from '../models/User';
|
||||||
|
// Form
|
||||||
|
import useForm from '../hooks';
|
||||||
|
|
||||||
|
interface InitFormData {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps extends WithFirebaseProps<User> {
|
||||||
|
isEmpty: boolean;
|
||||||
|
isLoaded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign in form
|
* Sign in form
|
||||||
*/
|
*/
|
||||||
const SignIn: FC = () => (
|
const SignIn: FC<IProps> = ({firebase, isEmpty, isLoaded}) => {
|
||||||
<section className="container">
|
const [error, setError] = useState<any>(null);
|
||||||
<div className="alert alert-danger">Invalid credentials</div>
|
|
||||||
<Header title="Sign In" lead="Sign into your account" />
|
|
||||||
<form action="dashboard.html" className="form">
|
|
||||||
<div className="form-group">
|
|
||||||
<input type="email" placeholder="Email Address" />
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<input type="password" placeholder="Password" minLength={6} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="submit" value="Login" className="btn btn-primary" />
|
// handle form data
|
||||||
</form>
|
const initFormData: InitFormData = {
|
||||||
<p className="my-1">
|
email: '',
|
||||||
Don't have an account? <Link to={ROUTES.SIGN_UP}>Sign up</Link>
|
password: '',
|
||||||
</p>
|
};
|
||||||
</section>
|
const {formData, handleChange, resetForm} = useForm<InitFormData>(
|
||||||
);
|
initFormData,
|
||||||
|
);
|
||||||
|
const {email, password} = formData;
|
||||||
|
|
||||||
export default SignIn;
|
// prevent submitting invalid forms
|
||||||
|
const isDisabled: boolean = email === '' || password === '';
|
||||||
|
|
||||||
|
/** create user with password */
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
firebase
|
||||||
|
.login({email, password})
|
||||||
|
.then(() => resetForm())
|
||||||
|
.catch(err => setError(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginWithGoogle = () =>
|
||||||
|
firebase.login({provider: 'google', type: 'popup'});
|
||||||
|
|
||||||
|
// redirect to dashboard if connected
|
||||||
|
if (isLoaded && !isEmpty) {
|
||||||
|
return <Redirect to={ROUTES.DASHBOARD} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="container">
|
||||||
|
{error && <Alert text={error?.message} />}
|
||||||
|
<Header title="Sign In" lead="Sign into your account" />
|
||||||
|
<GoogleButton type="light" className="my-1" onClick={loginWithGoogle} />
|
||||||
|
<form onSubmit={handleSubmit} className="form">
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
name="email"
|
||||||
|
value={email}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Email Address"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
name="password"
|
||||||
|
value={password}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
minLength={6}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Login"
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<p className="my-1">
|
||||||
|
Don't have an account? <Link to={ROUTES.SIGN_UP}>Sign up</Link>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** subscribe to store and firebase */
|
||||||
|
const enhance = compose<FC<IProps>>(connect(selectProfile), withFirebase);
|
||||||
|
|
||||||
|
export default enhance(SignIn);
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,145 @@
|
||||||
import React, {FC} from 'react';
|
import React, {FC, useState} from 'react';
|
||||||
import {Link} from 'react-router-dom';
|
// Routing
|
||||||
import Header from '../components/Header';
|
import {Link, Redirect} from 'react-router-dom';
|
||||||
import * as ROUTES from '../constants/routes';
|
import * as ROUTES from '../constants/routes';
|
||||||
/**
|
// Redux
|
||||||
* Sign up form
|
import {compose} from 'redux';
|
||||||
*/
|
import {connect} from 'react-redux';
|
||||||
const SignUp: FC = () => (
|
import {withFirebase, WithFirebaseProps} from 'react-redux-firebase';
|
||||||
<section className="container">
|
import {selectProfile} from '../store/firebase';
|
||||||
<Header title="Sign Up" lead="Create your account" />
|
import User, {newUser} from '../models/User';
|
||||||
<form action="dashboard.html" className="form">
|
// Style
|
||||||
<div className="form-group">
|
import GoogleButton from 'react-google-button';
|
||||||
<input type="text" placeholder="Name" required />
|
import Alert from '../components/Alert';
|
||||||
</div>
|
import Header from '../components/Header';
|
||||||
<div className="form-group">
|
// Form
|
||||||
<input type="email" placeholder="Email Address" />
|
import useForm from '../hooks';
|
||||||
<small className="form-text">
|
|
||||||
This site uses Gravatar, so use a Gravatar email.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<input type="password" placeholder="Password" minLength={6} />
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<input type="password" placeholder="Confirm Password" minLength={6} />
|
|
||||||
</div>
|
|
||||||
<input type="submit" value="Register" className="btn btn-primary" />
|
|
||||||
</form>
|
|
||||||
<p className="my-1">
|
|
||||||
Already have an account? <Link to={ROUTES.SIGN_IN}>Sign in</Link>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default SignUp;
|
// extends withFirebaseProps type to ad profile info
|
||||||
|
interface IProps extends WithFirebaseProps<User> {
|
||||||
|
isEmpty: boolean;
|
||||||
|
isLoaded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitFormData {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
password2: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign up form recieves firebase from withFirebase HOC
|
||||||
|
*/
|
||||||
|
const SignUp: FC<IProps> = ({firebase, isEmpty, isLoaded}) => {
|
||||||
|
const [error, setError] = useState<any>(null);
|
||||||
|
|
||||||
|
// handle form data
|
||||||
|
const initFormData: InitFormData = {
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
password2: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const {formData, handleChange, resetForm} = useForm<InitFormData>(
|
||||||
|
initFormData,
|
||||||
|
);
|
||||||
|
|
||||||
|
const {name, email, password, password2} = formData;
|
||||||
|
|
||||||
|
// prevent submitting invalid forms
|
||||||
|
const isDisabled: boolean = name === '' || email === '' || password === '';
|
||||||
|
|
||||||
|
/** create user with password */
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// pass the info to store into the second argument
|
||||||
|
firebase
|
||||||
|
.createUser({email, password}, newUser(name, email))
|
||||||
|
.then(() => resetForm())
|
||||||
|
.catch(err => setError(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginWithGoogle = () =>
|
||||||
|
firebase.login({provider: 'google', type: 'popup'});
|
||||||
|
|
||||||
|
// redirect to dashboard if connected
|
||||||
|
if (isLoaded && !isEmpty) {
|
||||||
|
return <Redirect to={ROUTES.DASHBOARD} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="container">
|
||||||
|
{error && <Alert text={error?.message} />}
|
||||||
|
<Header title="Sign Up" lead="Create your account" />
|
||||||
|
<GoogleButton type="light" className="my-1" onClick={loginWithGoogle} />
|
||||||
|
<form className="form" onSubmit={handleSubmit}>
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
name="name"
|
||||||
|
value={name}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
name="email"
|
||||||
|
value={email}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Email Address"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<small className="form-text">
|
||||||
|
This site uses Gravatar, so use a Gravatar email.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
name="password"
|
||||||
|
value={password}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
minLength={6}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
name="password2"
|
||||||
|
value={password2}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
type="password"
|
||||||
|
minLength={6}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="Register"
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={isDisabled}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<p className="my-1">
|
||||||
|
Already have an account? <Link to={ROUTES.SIGN_IN}>Sign in</Link>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** subscribe to store and firebase */
|
||||||
|
const enhance = compose<FC<IProps>>(connect(selectProfile), withFirebase);
|
||||||
|
|
||||||
|
export default enhance(SignUp);
|
||||||
|
|
|
||||||
7
src/pages/__tests__/SignUp.spec.tsx
Normal file
7
src/pages/__tests__/SignUp.spec.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import SignUp from '../SignUp';
|
||||||
|
|
||||||
|
describe('Signup Page', () => {
|
||||||
|
it('calls loadUser function', () => {});
|
||||||
|
it('redirects to dashboard if signed up', () => {});
|
||||||
|
it('call signup function on click', () => {});
|
||||||
|
});
|
||||||
48
src/router/PrivateRoute.tsx
Normal file
48
src/router/PrivateRoute.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, {FC} from 'react';
|
||||||
|
// Routing
|
||||||
|
import {Route, Redirect} from 'react-router-dom';
|
||||||
|
import * as ROUTES from '../constants/routes';
|
||||||
|
// Redux
|
||||||
|
import {isLoaded, isEmpty} from 'react-redux-firebase';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
|
import {RootState} from '../store';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
exact?: boolean;
|
||||||
|
path: string;
|
||||||
|
component: React.FC<any>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Redirects to the login screen if you're not authenticated yet or
|
||||||
|
* if auth is not loaded yet
|
||||||
|
*/
|
||||||
|
const PrivateRoute: FC<IProps> = ({
|
||||||
|
component: Component,
|
||||||
|
exact,
|
||||||
|
path,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const auth = useSelector((state: RootState) => state.firebase.auth);
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
exact={exact}
|
||||||
|
path={path}
|
||||||
|
{...rest}
|
||||||
|
render={({location, ...rest}) =>
|
||||||
|
isLoaded(auth) && !isEmpty(auth) ? (
|
||||||
|
<Component {...rest} />
|
||||||
|
) : (
|
||||||
|
<Redirect
|
||||||
|
to={{
|
||||||
|
pathname: ROUTES.SIGN_IN,
|
||||||
|
state: {from: location},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** subscribe to store and firebase */
|
||||||
|
export default PrivateRoute;
|
||||||
|
|
@ -13,6 +13,7 @@ import PostPage from '../pages/Post';
|
||||||
import Posts from '../pages/Posts';
|
import Posts from '../pages/Posts';
|
||||||
import NotFound from '../pages/NotFound';
|
import NotFound from '../pages/NotFound';
|
||||||
import * as ROUTES from '../constants/routes';
|
import * as ROUTES from '../constants/routes';
|
||||||
|
import PrivateRoute from './PrivateRoute';
|
||||||
|
|
||||||
/** Register navigation paths accessible */
|
/** Register navigation paths accessible */
|
||||||
const Router: FC = () => (
|
const Router: FC = () => (
|
||||||
|
|
@ -22,13 +23,16 @@ const Router: FC = () => (
|
||||||
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
|
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
|
||||||
<Route exact path={ROUTES.DEVELOPERS} component={Developers} />
|
<Route exact path={ROUTES.DEVELOPERS} component={Developers} />
|
||||||
<Route exact path={ROUTES.PROFILE} component={Profile} />
|
<Route exact path={ROUTES.PROFILE} component={Profile} />
|
||||||
<Route exact path={ROUTES.EDIT_PROFILE} component={EditProfile} />
|
<PrivateRoute exact path={ROUTES.EDIT_PROFILE} component={EditProfile} />
|
||||||
<Route exact path={ROUTES.DASHBOARD} component={Dashboard} />
|
<PrivateRoute exact path={ROUTES.DASHBOARD} component={Dashboard} />
|
||||||
<Route exact path={ROUTES.ADD_EXPERIENCE} component={AddExperience} />
|
<PrivateRoute
|
||||||
<Route exact path={ROUTES.ADD_EDUCATION} component={AddEducation} />
|
exact
|
||||||
<Route exact path={ROUTES.POST} component={PostPage} />
|
path={ROUTES.ADD_EXPERIENCE}
|
||||||
<Route exact path={ROUTES.POSTS} component={Posts} />
|
component={AddExperience}
|
||||||
<Route exact path={ROUTES.POSTS} component={Posts} />
|
/>
|
||||||
|
<PrivateRoute exact path={ROUTES.ADD_EDUCATION} component={AddEducation} />
|
||||||
|
<PrivateRoute exact path={ROUTES.POST} component={PostPage} />
|
||||||
|
<PrivateRoute exact path={ROUTES.POSTS} component={Posts} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
548
src/static/css/style.min.css
vendored
548
src/static/css/style.min.css
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
||||||
@import "_config";
|
@import '_config';
|
||||||
|
|
||||||
// Backgrounds
|
// Backgrounds
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9558,6 +9558,13 @@ react-error-overlay@^6.0.7:
|
||||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
|
||||||
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
|
||||||
|
|
||||||
|
react-google-button@^0.7.1:
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-google-button/-/react-google-button-0.7.1.tgz#e84b4ce270a66e345489dc86e47235e877fcc81a"
|
||||||
|
integrity sha512-FN8/9Va6oAGKL561yddNzcjz0iGlt2GZFwiDPczEu0fDVnR21/nBrPs7Y5x97V1S4//nvtkvv6ohfwtxIHpbfg==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
|
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.9.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue