Navigation (#4)

* specification test

* set public and private links sets based on authentication state

* enable frontend navigation

* set frontend link in sign in/up and landing pages

* refactor navbar tests

* style 404 page
This commit is contained in:
Ruidy 2020-05-13 10:59:51 +02:00 committed by GitHub
parent cdba48cc72
commit 1bde399408
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 253 additions and 35 deletions

2
.gitignore vendored
View file

@ -8,6 +8,8 @@
# testing
/coverage
/cypress/integration/examples
/cypress/fixtures/
/cypress/screenshots/
# production
/build

View file

@ -1,10 +1,10 @@
import React from 'react';
import React, {FC} from 'react';
import {BrowserRouter} from 'react-router-dom';
import NavBar from './components/NavBar';
import Router from './router/Router';
/** Main App container */
const App = () => {
const App: FC = () => {
return (
<BrowserRouter>
<NavBar />

View file

@ -4,6 +4,10 @@ import {
faUser,
faCodeBranch,
faGraduationCap,
faExclamation,
faExclamationCircle,
faExclamationTriangle,
faCode,
} from '@fortawesome/free-solid-svg-icons';
import {faConnectdevelop} from '@fortawesome/free-brands-svg-icons';
@ -30,9 +34,15 @@ const Header: FC<IProps> = ({title, lead, icon = 'faUser'}) => {
if (icon === 'code-branch') {
return <FontAwesomeIcon icon={faCodeBranch} />;
}
if (icon === 'code') {
return null;
}
if (icon === 'graduation-cap') {
return <FontAwesomeIcon icon={faGraduationCap} />;
}
if (icon === 'not-found') {
return <FontAwesomeIcon icon={faExclamationTriangle} />;
}
};
return (

View file

@ -1,29 +1,77 @@
import React, {FC} from 'react';
import {Link} from 'react-router-dom';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCode} from '@fortawesome/free-solid-svg-icons';
import {faCode, faSignOutAlt, faUser} from '@fortawesome/free-solid-svg-icons';
import * as ROUTES from '../constants/routes';
interface IProps {
isAuthenticated?: boolean;
loading?: boolean;
}
/**
* Main Navbar serves navigation routes.
*/
const NavBar: FC = () => (
<nav className="navbar bg-dark">
<h1>
<a href="dashboard.html">
<FontAwesomeIcon icon={faCode} /> DevBook
</a>
</h1>
<ul>
const NavBar: FC<IProps> = ({isAuthenticated = false, loading = false}) => {
const publicLinks = (
<ul data-testid="publicLinks">
<li>
<a href="profiles.html">Developers</a>
<Link to={ROUTES.DEVELOPERS} data-testid="devsLink">
Developers
</Link>
</li>
<li>
<a href="register.html">Register</a>
<Link to={ROUTES.SIGN_UP} data-testid="signupLink">
Register
</Link>
</li>
<li>
<a href="login.html">Login</a>
<Link to={ROUTES.SIGN_IN} data-testid="loginLink">
Login
</Link>
</li>
</ul>
</nav>
);
);
const privateLinks = (
<ul data-testid="privateLinks">
<li>
<Link to={ROUTES.DEVELOPERS} data-testid="devsLink">
Developers
</Link>
</li>
<li>
<Link to={ROUTES.POSTS} data-testid="postsLink">
Posts
</Link>
</li>
<li>
<Link to={ROUTES.DASHBOARD} data-testid="dashboardLink">
<FontAwesomeIcon icon={faUser} />
<span className="hide-sm"> Dashboard</span>
</Link>
</li>
<li>
<Link to={ROUTES.SIGN_IN} data-testid="logoutLink">
<FontAwesomeIcon icon={faSignOutAlt} />
<span className="hide-sm"> Log out</span>
</Link>
</li>
</ul>
);
/** Display appropriated links after loading given authenticated prop */
const RenderLinks = !loading && isAuthenticated ? privateLinks : publicLinks;
return (
<nav className="navbar bg-dark">
<h1>
<Link to={ROUTES.LANDING} data-testid="homeLink">
<FontAwesomeIcon icon={faCode} /> DevBook
</Link>
</h1>
{RenderLinks}
</nav>
);
};
export default NavBar;

View file

@ -0,0 +1,110 @@
import React from 'react';
import {BrowserRouter} from 'react-router-dom';
import NavBar from '../NavBar';
import {render, RenderResult} from '@testing-library/react';
interface IProps {
isAuthenticated: boolean;
loading: boolean;
}
describe('Navbar displays', () => {
let context: RenderResult;
let navProps: IProps;
describe('before loading', () => {
navProps = {
isAuthenticated: false,
loading: true,
};
beforeEach(() => {
context = render(
<BrowserRouter>
<NavBar {...navProps} />
</BrowserRouter>,
);
});
test('landing page link', () => {
const {getAllByTestId} = context;
const link = getAllByTestId('homeLink');
expect(link[0]).toBeTruthy();
});
it('no links while loading', () => {
const {queryByTestId} = context;
const links = queryByTestId('privateLinks');
expect(links).toBeNull();
});
});
describe('when loaded', () => {
describe('when user is not authenticated', () => {
navProps = {
isAuthenticated: false,
loading: false,
};
beforeEach(() => {
context = render(
<BrowserRouter>
<NavBar {...navProps} />
</BrowserRouter>,
);
});
test('developers link', () => {
const {getAllByTestId} = context;
const link = getAllByTestId('devsLink');
expect(link[0]).toBeTruthy();
});
test('register link', () => {
const {getAllByTestId} = context;
const link = getAllByTestId('signupLink');
expect(link[0]).toBeTruthy();
});
test('login page link', () => {
const {getAllByTestId} = context;
const link = getAllByTestId('loginLink');
expect(link[0]).toBeTruthy();
});
});
// describe('when user is authenticated', () => {
// navProps = {
// isAuthenticated: true,
// loading: false,
// };
// beforeEach(() => {
// context = render(
// <BrowserRouter>
// <NavBar {...navProps} />
// </BrowserRouter>,
// );
// });
// test('developers link', () => {
// const {getAllByTestId} = context;
// const link = getAllByTestId('devsLink');
// expect(link[0]).toBeTruthy();
// });
// // test('posts page link', () => {
// // const {getAllByTestId} = context;
// // const link = getAllByTestId('postsLink');
// // expect(link[0]).toBeTruthy();
// // });
// // test('dashboard page link', () => {
// // const {getAllByTestId} = context;
// // const link = getAllByTestId('dashboardLink');
// // expect(link[0]).toBeTruthy();
// // });
// // test('logout page link', () => {
// // const {getAllByTestId} = context;
// // const link = getAllByTestId('logoutLink');
// // expect(link[0]).toBeTruthy();
// // });
// // });
// });
});
});

View file

@ -1,4 +1,7 @@
import React, {FC} from 'react';
import {Link} from 'react-router-dom';
import * as ROUTES from '../constants/routes';
import Header from '../components/Header';
/**
* Landing page
@ -7,18 +10,18 @@ const Landing: FC = () => (
<section className="landing">
<div className="dark-overlay">
<div className="landing-inner">
<h1 className="x-large">DevBook</h1>
<p className="lead">
Create developer profiles, portfolio, share and get help from other
devs
</p>
<Header
title="DevBook"
lead="Create developer profiles, portfolio, share and get help from other devs"
icon="code"
/>
<div className="buttons">
<a href="register.html" className="btn btn-primary">
<Link to={ROUTES.SIGN_UP} className="btn btn-primary">
Sign up
</a>
<a href="login.html" className="btn btn-light">
</Link>
<Link to={ROUTES.SIGN_IN} className="btn btn-light">
Login
</a>
</Link>
</div>
</div>
</div>

28
src/pages/NotFound.tsx Normal file
View file

@ -0,0 +1,28 @@
import React, {FC} from 'react';
import {Link} from 'react-router-dom';
import Header from '../components/Header';
import * as ROUTES from '../constants/routes';
const NotFound: FC = () => (
<section className="not-found">
<div className="dark-overlay">
<div className="landing-inner">
<Header
title="Nothing Here"
lead="Sorry the page requested does not exist."
icon="not-found"
/>
<div className="buttons">
<Link to={ROUTES.SIGN_UP} className="btn btn-primary">
Sign up
</Link>
<Link to={ROUTES.SIGN_IN} className="btn btn-light">
Login
</Link>
</div>
</div>
</div>
</section>
);
export default NotFound;

View file

@ -1,5 +1,7 @@
import React, {FC} from 'react';
import * as ROUTES from '../constants/routes';
import Header from '../components/Header';
import {Link} from 'react-router-dom';
/**
* Sign in form
@ -19,7 +21,7 @@ const SignIn: FC = () => (
<input type="submit" value="Login" className="btn btn-primary" />
</form>
<p className="my-1">
Don't have an account? <a href="register.html">Sign in</a>
Don't have an account? <Link to={ROUTES.SIGN_UP}>Sign up</Link>
</p>
</section>
);

View file

@ -1,6 +1,7 @@
import React, {FC} from 'react';
import {Link} from 'react-router-dom';
import Header from '../components/Header';
import * as ROUTES from '../constants/routes';
/**
* Sign up form
*/
@ -26,7 +27,7 @@ const SignUp: FC = () => (
<input type="submit" value="Register" className="btn btn-primary" />
</form>
<p className="my-1">
Already have an account? <a href="login.html">Sign in</a>
Already have an account? <Link to={ROUTES.SIGN_IN}>Sign in</Link>
</p>
</section>
);

View file

@ -11,6 +11,7 @@ import AddExperience from '../pages/AddExperience';
import AddEducation from '../pages/AddEducation';
import PostPage from '../pages/Post';
import Posts from '../pages/Posts';
import NotFound from '../pages/NotFound';
import * as ROUTES from '../constants/routes';
/** Register navigation paths accessible */
@ -27,6 +28,8 @@ const Router: FC = () => (
<Route exact path={ROUTES.ADD_EDUCATION} component={AddEducation} />
<Route exact path={ROUTES.POST} component={PostPage} />
<Route exact path={ROUTES.POSTS} component={Posts} />
<Route exact path={ROUTES.POSTS} component={Posts} />
<Route component={NotFound} />
</Switch>
);

View file

@ -540,3 +540,8 @@ img {
.post img {
width: 150px;
}
.not-found {
position: relative;
background: url('../img/404.jpg') no-repeat center center/cover;
height: 100vh;
}

BIN
src/static/img/404.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

View file

@ -1,7 +1,7 @@
@import "_config";
@import "_utils";
@import "_form";
@import "_mobile";
@import '_config';
@import '_utils';
@import '_form';
@import '_mobile';
* {
box-sizing: border-box;
@ -10,7 +10,7 @@
}
body {
font-family: "Raleway", sans-serif;
font-family: 'Raleway', sans-serif;
font-size: 1rem;
line-height: 1.6;
background-color: white;
@ -62,7 +62,7 @@ img {
// landing
.landing {
position: relative;
background: url("../img/showcase.jpg") no-repeat center center/cover;
background: url('../img/showcase.jpg') no-repeat center center/cover;
height: 100vh;
&-inner {
@ -93,7 +93,7 @@ img {
.profile-grid {
display: grid;
grid-template-areas: "top top" "about about" "exp edu" "github github";
grid-template-areas: 'top top' 'about about' 'exp edu' 'github github';
grid-gap: 1rem;
.profile-top {
@ -190,3 +190,9 @@ img {
width: 150px;
}
}
.not-found {
position: relative;
background: url('../img/404.jpg') no-repeat center center/cover;
height: 100vh;
}