General layout (#3)

* specify layout

* create components folder and Navbar

* create pages folder and Landing page component

* create Sign Up page component

* set basic routing

- install react-router-dom
- create Router folder and component

* add constant routes file

* verify all routes are accessible

* add signin page

* extract header component

* add developers page

* extract dev profiles

* extract DevSummary type

* update tests

* add types

* lay profile top and about out

* lay experience section out

- install moment
- define Experience interface
- define TimePeriod type & method

* lay education section out

- define education interface

* lay repos section  out

* add Dashboard page and test

* lay dashboard top section out

* [refactor] Experience.ts: change employer to company; move TimePeriod to its own file

* experience credential table in dashboard

* education credential table in dashboard

* dashboard done

* edit profile

* add experience page

* add education page

* create Comment and Post types; PostPage

* postpage

* posts page

* refactor
This commit is contained in:
Ruidy 2020-05-12 23:09:40 +02:00 committed by GitHub
parent 9e59bb0f2c
commit cdba48cc72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1352 additions and 58 deletions

View file

@ -0,0 +1,6 @@
describe('App Layout', () => {
it('contains a navbar', () => {
cy.visit('/');
cy.get('nav');
});
});

View file

@ -0,0 +1,58 @@
import * as ROUTES from '../../src/constants/routes';
describe('App Router', () => {
it('contains Landing page', () => {
cy.visit(ROUTES.LANDING);
cy.get('section');
});
it('contains SignUp page', () => {
cy.visit(ROUTES.SIGN_UP);
cy.get('section');
});
it('contains SignIn page', () => {
cy.visit(ROUTES.SIGN_IN);
cy.get('section');
});
it('contains Developers page', () => {
cy.visit(ROUTES.DEVELOPERS);
cy.get('section');
});
it('contains Profile page', () => {
cy.visit(ROUTES.PROFILE);
cy.get('section');
});
it('contains Edit Profile page', () => {
cy.visit(ROUTES.EDIT_PROFILE);
cy.get('section');
});
it('contains Add Experience page', () => {
cy.visit(ROUTES.ADD_EXPERIENCE);
cy.get('section');
});
it('contains Add Education page', () => {
cy.visit(ROUTES.ADD_EDUCATION);
cy.get('section');
});
it('contains Dashboard page', () => {
cy.visit(ROUTES.DASHBOARD);
cy.get('section');
});
it('contains Post page', () => {
cy.visit(ROUTES.POST);
cy.get('section');
});
it('contains Posts page', () => {
cy.visit(ROUTES.POSTS);
cy.get('section');
});
});

View file

@ -16,9 +16,12 @@
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.5",
"cypress": "^4.5.0",
"moment": "^2.25.3",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"typescript": "~3.7.2"
},

View file

@ -1,47 +1,15 @@
import React from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCode} from '@fortawesome/free-solid-svg-icons';
import {BrowserRouter} from 'react-router-dom';
import NavBar from './components/NavBar';
import Router from './router/Router';
/** Main App container */
const App = () => {
return (
<>
<nav className="navbar bg-dark">
<h1>
<a href="dashboard.html">
<FontAwesomeIcon icon={faCode} /> {' '} DevBook
</a>
</h1>
<ul>
<li>
<a href="profiles.html">Developers</a>
</li>
<li>
<a href="register.html">Register</a>
</li>
<li>
<a href="login.html">Login</a>
</li>
</ul>
</nav>
<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>
<div className="buttons">
<a href="register.html" className="btn btn-primary">
Sign up
</a>
<a href="login.html" className="btn btn-light">
Login
</a>
</div>
</div>
</div>
</section>
</>
<BrowserRouter>
<NavBar />
<Router />
</BrowserRouter>
);
};

View file

@ -0,0 +1,38 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck} from '@fortawesome/free-solid-svg-icons';
import {DevSummary} from '../models/Dev';
/**
* Present a dev profile succintly. Redirect to dev profile on click.
* @param props DevSummary object
*/
const DevProfile: FC<DevSummary> = ({
id,
name,
picture,
description,
location,
skills,
}) => (
<div className="profile bg-light">
<img src={picture} alt={name} className="round-img" />
<div>
<h2>{name}</h2>
<p>{description}</p>
<p>{location}</p>
<a href="profile.html" className="btn btn-primary">
View Profile
</a>
</div>
<ul>
{skills.map((s, i) => (
<li className="text-primary" key={i}>
<FontAwesomeIcon icon={faCheck} /> {s}
</li>
))}
</ul>
</div>
);
export default DevProfile;

View file

@ -0,0 +1,18 @@
import React, {FC} from 'react';
import Header from './Header';
interface IProps {
title: string;
lead: string;
icon?: string;
}
/** Header component displayed on form pages */
const FormHeader: FC<IProps> = props => (
<>
<Header {...props} />
<small>* marks required fields</small>
</>
);
export default FormHeader;

48
src/components/Header.tsx Normal file
View file

@ -0,0 +1,48 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faUser,
faCodeBranch,
faGraduationCap,
} from '@fortawesome/free-solid-svg-icons';
import {faConnectdevelop} from '@fortawesome/free-brands-svg-icons';
interface IProps {
title: string;
lead: string;
icon?: string;
}
/**
* Header component
* @param title of the page
* @param lead description of the content
* @param icon to display (optional and default to faUser)
*/
const Header: FC<IProps> = ({title, lead, icon = 'faUser'}) => {
const RenderIcon = (icon: string) => {
if (icon === 'faUser') {
return <FontAwesomeIcon icon={faUser} />;
}
if (icon === 'connectdevelop') {
return <FontAwesomeIcon icon={faConnectdevelop} />;
}
if (icon === 'code-branch') {
return <FontAwesomeIcon icon={faCodeBranch} />;
}
if (icon === 'graduation-cap') {
return <FontAwesomeIcon icon={faGraduationCap} />;
}
};
return (
<>
<h1 className="large text-primary">{title}</h1>
<p className="lead">
{RenderIcon(icon)} {lead}
</p>
</>
);
};
export default Header;

29
src/components/NavBar.tsx Normal file
View file

@ -0,0 +1,29 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCode} from '@fortawesome/free-solid-svg-icons';
/**
* 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>
<li>
<a href="profiles.html">Developers</a>
</li>
<li>
<a href="register.html">Register</a>
</li>
<li>
<a href="login.html">Login</a>
</li>
</ul>
</nav>
);
export default NavBar;

15
src/constants/routes.ts Normal file
View file

@ -0,0 +1,15 @@
/**
* Register all routes here for easy future modification.
* Paths must start with '/'
*/
export const LANDING: string = '/';
export const SIGN_UP: string = '/signup';
export const SIGN_IN: string = '/signin';
export const DEVELOPERS: string = '/developers';
export const PROFILE: string = '/profile';
export const EDIT_PROFILE: string = '/edit-profile';
export const DASHBOARD: string = '/dashboard';
export const ADD_EXPERIENCE: string = '/add-experience';
export const ADD_EDUCATION: string = '/add-education';
export const POST: string = '/post';
export const POSTS: string = '/posts';

106
src/models/Dev.ts Normal file
View file

@ -0,0 +1,106 @@
import Experience from '../types/Experience';
import Education from '../types/Education';
import Repo from '../types/Repo';
/** Shorter dev interface */
export interface DevSummary {
id: string;
name: string;
picture: string;
description: string;
location: string;
skills: string[];
}
/** Full developer profile information.
* @extends DevSummary to avoid duplication
*/
interface Dev extends DevSummary {
bio: string;
links: Object;
experiences: Experience[];
educations: Education[];
repos: Repo[];
}
/**
* sample Dev for development and tests
*/
export const dummyDev: Dev = {
id: '0',
name: 'John Doe',
picture:
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
description: 'Developer at Microsoft',
location: 'Seattle, WA',
skills: ['HTML', 'CSS', 'JavaScript', 'Python'],
links: {
web: '#',
instagram: 'http://insta.com',
facebook: '#',
linkedin: '#',
twitter: '#',
github: '#',
},
bio:
'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Blanditiis unde quae vero enim adipisci voluptas magni sapiente reprehenderit error minima.',
experiences: [
{
company: 'Microsoft',
from: new Date(2011, 10),
to: 'Current',
position: 'Senior Developer',
description:
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptas corrupti rem eius, accusantium ipsum vel eveniet magnam voluptatum? Minus, voluptatum!',
},
{
company: 'Sun Microsystems',
from: new Date(2004, 10),
to: new Date(2010, 11),
position: 'System Admin',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus at rem totam sed qui! Quas.',
},
],
educations: [
{
school: 'University of Washington',
from: new Date(1993, 9),
to: new Date(1999, 6),
degree: 'Master',
field: 'Computer Science',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellendus at rem totam sed qui! Quas.',
},
],
repos: [
{
name: 'Repo #1',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit,deserunt.',
link: '#',
stars: 42,
watchers: 2,
forks: 4,
},
{
name: 'Repo #2',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit,deserunt.',
link: '#',
stars: 21,
watchers: 1,
forks: 2,
},
{
name: 'Repo #3',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Fugit,deserunt.',
link: '#',
stars: 50,
watchers: 32,
forks: 12,
},
],
};
export default Dev;

47
src/models/Post.ts Normal file
View file

@ -0,0 +1,47 @@
import Comment from '../types/Comment';
/**
* Post send by a dev
*/
interface Post {
id: string;
userID: string;
name: string;
text: string;
picture: string;
likes: string[];
comments: Comment[];
// date: Date;
}
/**
* sample Post for development and tests
*/
export const dummyPost: Post = {
id: '12',
userID: '42',
picture:
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
name: 'John Doe',
text:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint possimus corporis sunt necessitatibus! Minus nesciunt soluta suscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?',
comments: [
{
picture:
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
name: 'John Doe',
text:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sintpossimus corporis sunt necessitatibus! Minus nesciunt solutasuscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?',
},
{
picture:
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
name: 'Ruidy Nemo',
text:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sintpossimus corporis sunt necessitatibus! Minus nesciunt solutasuscipit nobis. Amet accusamus distinctio cupiditate blanditiis dolor? Illo perferendis eveniet cum cupiditate aliquam?',
},
],
likes: ['0', '42'],
};
export default Post;

View file

@ -0,0 +1,65 @@
import React, {FC} from 'react';
import FormHeader from '../components/FormHeader';
/**
* Form to add an Education step to Profile
*/
const AddEducation: FC = () => (
<section className="container">
<FormHeader
title="Add Your Education"
lead="Add any school, bootcamp, etc that
you have attended"
icon="graduation-cap"
/>
<form className="form">
<div className="form-group">
<input
type="text"
placeholder="* School or Bootcamp"
name="school"
required
/>
</div>
<div className="form-group">
<input
type="text"
placeholder="* Degree or Certificate"
name="degree"
required
/>
</div>
<div className="form-group">
<input type="text" placeholder="Field Of Study" name="fieldofstudy" />
</div>
<div className="form-group">
<h4>From Date</h4>
<input type="date" name="from" />
</div>
<div className="form-group">
<h4>To Date</h4>
<input type="date" name="to" />
</div>
<div className="form-group">
<p>
<input type="checkbox" name="current" value="" /> Current School
</p>
</div>
<div className="form-group">
<textarea
name="description"
cols={30}
rows={5}
placeholder="Program Description"
></textarea>
</div>
<input type="submit" className="btn btn-primary my-1" value="Submit" />
<a className="btn btn-light my-1" href="dashboard.html">
Go Back
</a>
</form>
</section>
);
export default AddEducation;

View file

@ -0,0 +1,57 @@
import React, {FC} from 'react';
import FormHeader from '../components/FormHeader';
/**
* Form to add an Education step to Profile
*/
const AddExperience: FC = () => {
return (
<section className="container">
<FormHeader
title="Add An Experience"
lead="Add any developer/programming
positions that you have had in the past"
icon="code-branch"
/>
<form className="form">
<div className="form-group">
<input type="text" placeholder="* Job Title" name="title" required />
</div>
<div className="form-group">
<input type="text" placeholder="* Company" name="company" required />
</div>
<div className="form-group">
<input type="text" placeholder="Location" name="location" />
</div>
<div className="form-group">
<h4>From Date</h4>
<input type="date" name="from" />
</div>
<div className="form-group">
<h4>To Date</h4>
<input type="date" name="to" />
</div>
<div className="form-group">
<p>
<input type="checkbox" name="current" value="" /> Current Job
</p>
</div>
<div className="form-group">
<textarea
name="description"
cols={30}
rows={5}
placeholder="Job Description"
></textarea>
</div>
<input type="submit" className="btn btn-primary my-1" value="Submit" />
<a className="btn btn-light my-1" href="dashboard.html">
Go Back
</a>
</form>
</section>
);
};
export default AddExperience;

90
src/pages/Dashboard.tsx Normal file
View file

@ -0,0 +1,90 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faUserCircle,
faGraduationCap,
faUserSlash,
} from '@fortawesome/free-solid-svg-icons';
import {faBlackTie} from '@fortawesome/free-brands-svg-icons';
import Header from '../components/Header';
import Dev, {dummyDev as dev} from '../models/Dev';
import Experience from '../types/Experience';
import {getTimePeriod} from '../types/TimePeriod';
import Education from '../types/Education';
/**
* Main page from which a Dev can peek and edit its own profile.
*/
const Dashboard: FC<Dev> = () => {
return (
<section className="container">
<Header title="Dashboard" lead={`Welcome ${dev.name}`} />
<div className="dash-buttons">
<a href="create-profile.html" className="btn btn-light">
<FontAwesomeIcon icon={faUserCircle} /> Edit Profile
</a>
<a href="add-experience.html" className="btn btn-light">
<FontAwesomeIcon icon={faBlackTie} /> Add Experience
</a>
<a href="add-education.html" className="btn btn-light">
<FontAwesomeIcon icon={faGraduationCap} /> Add Education
</a>
</div>
<h2 className="my-2">Experience Credentials</h2>
<table className="table">
<thead>
<tr>
<th>Company</th>
<th className="hide-sm">Title</th>
<th className="hide-sm">Years</th>
<th></th>
</tr>
</thead>
<tbody>
{dev.experiences.map((exp: Experience, i: number) => (
<tr key={i}>
<td>{exp.company}</td>
<td className="hide-sm">{exp.position}</td>
<td className="hide-sm">{getTimePeriod(exp.from, exp.to)}</td>
<td>
<button className="btn btn-danger">Delete</button>
</td>
</tr>
))}
</tbody>
</table>
<h2 className="my-2">Education Credentials</h2>
<table className="table">
<thead>
<tr>
<th>School</th>
<th className="hide-sm">Degree</th>
<th className="hide-sm">Years</th>
<th></th>
</tr>
</thead>
<tbody>
{dev.educations.map((edu: Education, i: number) => (
<tr key={i}>
<td>{edu.school}</td>
<td className="hide-sm">{edu.field}</td>
<td className="hide-sm">{getTimePeriod(edu.from, edu.to)}</td>
<td>
<button className="btn btn-danger">Delete</button>
</td>
</tr>
))}
</tbody>
</table>
<div className="my-2">
<button className="btn btn-danger">
<FontAwesomeIcon icon={faUserSlash} /> Delete my Account
</button>
</div>
</section>
);
};
export default Dashboard;

49
src/pages/Developers.tsx Normal file
View file

@ -0,0 +1,49 @@
import React, {FC} from 'react';
import Header from '../components/Header';
import DevProfile from '../components/DevProfile';
import {DevSummary} from '../models/Dev';
/**
* Developers list page
*/
// const Developers: FC<DevSummary[]> = (developers) => {
const Developers: FC = () => {
const developers: DevSummary[] = [
{
id: '0',
name: 'John Doe',
picture:
'https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200',
description: 'Developer at Microsoft',
location: 'Seattle, WA',
skills: ['HTML', 'CSS', 'JavaScript', 'Python'],
},
{
id: '42',
name: 'Ruidy Nemausat',
picture:
'https://lh3.googleusercontent.com/a-/AOh14GhncH95MWKwPR3TRKy4eVd4n6w0-fobe4dhiam2xA',
description: 'Fullstack Engineer at DESY',
location: 'Hamburg, DE',
skills: ['React', 'TypeScript', 'Redux', 'Nodejs'],
},
];
return (
<section className="container">
<Header
title="Developers"
lead="Browse and connect with developers"
icon="connectdevelop"
/>
<div className="profiles">
{developers.map(dev => (
// use spread operator to pass props
<DevProfile key={dev.id} {...dev} />
))}
</div>
</section>
);
};
export default Developers;

121
src/pages/EditProfile.tsx Normal file
View file

@ -0,0 +1,121 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faTwitter,
faFacebook,
faYoutube,
faLinkedin,
faInstagram,
} from '@fortawesome/free-brands-svg-icons';
import FormHeader from '../components/FormHeader';
/**
* Form to update dev's personal information.
*/
const EditProfile: FC = () => {
return (
<section className="container">
<FormHeader
title="Create your profile"
lead="Let's get some information to make your profile stand out"
/>
<form className="form">
<div className="form-group">
<select name="status" required>
<option value="0">* Select Professional Status</option>
<option value="Developer">Developer</option>
<option value="Junior Developer">Junior Developer</option>
<option value="Senior Developer">Senior Developer</option>
<option value="Manager">Manager</option>
<option value="Student or Learning">Student or Learning</option>
<option value="Instructor">Instructor or Teacher</option>
<option value="Intern">Intern</option>
<option value="Other">Other</option>
</select>
<small className="form-text">
Give us an idea of where you are at in your career
</small>
</div>
<div className="form-group">
<input type="text" placeholder="Company" name="company" />
<small className="form-text">
Could be your own company or one you work for
</small>
</div>
<div className="form-group">
<input type="text" placeholder="Website" name="website" />
<small className="form-text">
Could be your own or a company website
</small>
</div>
<div className="form-group">
<input type="text" placeholder="Location" name="location" />
<small className="form-text">
City & state suggested (eg. Boston, MA)
</small>
</div>
<div className="form-group">
<input type="text" placeholder="* Skills" name="skills" required />
<small className="form-text">
Please use comma separated values (eg. HTML,CSS,JavaScript,PHP)
</small>
</div>
<div className="form-group">
<input
type="text"
placeholder="Github Username"
name="githubusername"
/>
<small className="form-text">
If you want your latest repos and a Github link, include your
username
</small>
</div>
<div className="form-group">
<textarea placeholder="A short bio of yourself" name="bio"></textarea>
<small className="form-text">Tell us a little about yourself</small>
</div>
<div className="my-2">
<button type="button" className="btn btn-light">
Add Social Network Links
</button>
<span>Optional</span>
</div>
<div className="form-group social-input">
<FontAwesomeIcon icon={faFacebook} size="2x" />
<input type="text" placeholder="Facebook URL" name="facebook" />
</div>
<div className="form-group social-input">
<FontAwesomeIcon icon={faInstagram} size="2x" />
<input type="text" placeholder="Instagram URL" name="instagram" />
</div>
<div className="form-group social-input">
<FontAwesomeIcon icon={faLinkedin} size="2x" />
<input type="text" placeholder="Linkedin URL" name="linkedin" />
</div>
<div className="form-group social-input">
<FontAwesomeIcon icon={faTwitter} size="2x" />
<input type="text" placeholder="Twitter URL" name="twitter" />
</div>
<div className="form-group social-input">
<FontAwesomeIcon icon={faYoutube} size="2x" />
<input type="text" placeholder="YouTube URL" name="youtube" />
</div>
<input type="submit" className="btn btn-primary my-1" value="Submit" />
<a className="btn btn-light my-1" href="dashboard.html">
Go Back
</a>
</form>
</section>
);
};
export default EditProfile;

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

@ -0,0 +1,28 @@
import React, {FC} from 'react';
/**
* Landing page
*/
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>
<div className="buttons">
<a href="register.html" className="btn btn-primary">
Sign up
</a>
<a href="login.html" className="btn btn-light">
Login
</a>
</div>
</div>
</div>
</section>
);
export default Landing;

59
src/pages/Post.tsx Normal file
View file

@ -0,0 +1,59 @@
import React, {FC} from 'react';
import Post, {dummyPost as post} from '../models/Post';
import Comment from '../types/Comment';
/**
* Display a Post and the related comments. Shows a form to add a comment.
*/
const PostPage: FC<Post> = () => (
<section className="container">
<a href="posts.html" className="btn btn-light">
Back To Posts
</a>
<div className="post bg-white p-1 my-1">
<div>
<a href="profile.html">
<img className="round-img" src={post.picture} alt={post.name} />
<h4>{post.name}</h4>
</a>
</div>
<div>
<p className="my-1">{post.text}</p>
</div>
</div>
<div className="post-form">
<div className="post-form-header bg-primary">
<h3>Leave A Comment</h3>
</div>
<form className="form my-1">
<textarea
name="text"
cols={30}
rows={5}
placeholder="Comment on this post"
></textarea>
<input type="submit" className="btn btn-dark my-1" value="Submit" />
</form>
</div>
<div className="posts">
{post.comments.map((c: Comment, i: number) => (
<div className="post bg-white p-1 my-1" key={i}>
<div>
<a href="profile.html">
<img className="round-img" src={c.picture} alt={c.name} />
<h4>{c.name}</h4>
</a>
</div>
<div>
<p className="my-1">{c.text}</p>
</div>
</div>
))}
</div>
</section>
);
export default PostPage;

58
src/pages/Posts.tsx Normal file
View file

@ -0,0 +1,58 @@
import React, {FC} from 'react';
import Post, {dummyPost as post} from '../models/Post';
import Header from '../components/Header';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faThumbsUp, faThumbsDown} from '@fortawesome/free-solid-svg-icons';
/**
* A Dev's Posts list
*/
const Posts: FC = () => {
const posts: Post[] = [post, post];
return (
<section className="container">
<Header title="Posts" lead="Welcome to the community" />
<div className="post-form">
<div className="post-form-header bg-primary">
<h3>Say Something</h3>
</div>
<form className="form my-1">
<textarea cols={30} rows={5} placeholder="Create a post"></textarea>
<input type="submit" value="Submit" className="btn btn-dark my-1" />
<div className="posts">
{posts.map((post: Post) => (
<div className="post bg-white p-1 my-1" key={post.id}>
<div>
<a href="profile.html">
<img
src={post.picture}
alt={post.name}
className="round-img"
/>
<h4>{post.name}</h4>
</a>
</div>
<div>
<p className="my-1">{post.text}</p>
<button className="btn btn-light">
<FontAwesomeIcon icon={faThumbsUp} /> {post.likes.length}
</button>
<button className="btn btn-light">
<FontAwesomeIcon icon={faThumbsDown} />
</button>
<a href="post.html" className="btn btn-primary">
Discussion
</a>
</div>
</div>
))}
</div>
</form>
</div>
</section>
);
};
export default Posts;

159
src/pages/Profile.tsx Normal file
View file

@ -0,0 +1,159 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faGithub,
faFacebook,
faInstagram,
faLinkedin,
faTwitter,
} from '@fortawesome/free-brands-svg-icons';
import {
faGlobe,
IconDefinition,
faCheck,
faStar,
faEye,
faCodeBranch,
} from '@fortawesome/free-solid-svg-icons';
import Dev, {dummyDev as dev} from '../models/Dev';
import Experience from '../types/Experience';
import {getTimePeriod} from '../types/TimePeriod';
import Education from '../types/Education';
import Repo from '../types/Repo';
/**
* Dev personal profile as seen by other people.
*/
const Profile: FC<Dev> = () => {
/** return the icon corresponding to the social name */
const renderSocialIcon = (name: string): IconDefinition => {
switch (name) {
case 'facebook':
return faFacebook;
case 'github':
return faGithub;
case 'instagram':
return faInstagram;
case 'linkedin':
return faLinkedin;
case 'twitter':
return faTwitter;
default:
return faGlobe;
}
};
return (
<section className="container">
<a href="profiles.html" className="btn">
Back to profiles
</a>
<div className="profile-grid my-1">
<div className="profile-top bg-primary p-2">
<img
src="https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200"
alt="Some guy"
className="round-img my-1"
/>
<h1 className="large">{dev.name}</h1>
<p className="lead">{dev.description}</p>
<p>{dev.location}</p>
<div className="icons my-1">
{Object.entries(dev.links).map(([icon, webAddress], i: number) => (
<a href={webAddress} key={i}>
<FontAwesomeIcon icon={renderSocialIcon(icon)} size="2x" />
</a>
))}
</div>
</div>
<div className="profile-about bg-light p-2">
<h2 className="text-primary">{`${dev.name}'s Bio`}</h2>
<p>{dev.bio}</p>
<div className="line"></div>
<h2 className="text-primary">Skill Set</h2>
<div className="skills">
{dev.skills.map((s: string, i: number) => (
<div className="p-1" key={i}>
<FontAwesomeIcon icon={faCheck} /> {s}
</div>
))}
</div>
</div>
<div className="profile-exp bg-white p-2">
<h2 className="text-primary">Experiences</h2>
{dev.experiences.map((exp: Experience, i: number) => (
<div key={i}>
<h3>{exp.company}</h3>
<p>{getTimePeriod(exp.from, exp.to)}</p>
<p>
<strong>Position: </strong>
{exp.position}
</p>
<p>
<strong>Description: </strong>
{exp.description}
</p>
</div>
))}
</div>
<div className="profile-edu bg-white p-2">
<h2 className="text-primary">Education</h2>
{dev.educations.map((edu: Education, i: number) => (
<div key={i}>
<h3>{edu.school}</h3>
<p>{getTimePeriod(edu.from, edu.to)}</p>
<p>
<strong>Degree: </strong>
{edu.degree}
</p>
<p>
<strong>Field: </strong>
{edu.field}
</p>
<p>
<strong>Description: </strong>
{edu.description}
</p>
</div>
))}
</div>
<div className="profile-github">
<h2 className="text-primary my-1">
<FontAwesomeIcon icon={faGithub} /> GitHub Repos
</h2>
{dev.repos.map((r: Repo, i: number) => (
<div className="repo bg-white my-1 p-1">
<div>
<h4>
<a href={r.link}>{r.name}</a>
</h4>
<p>{r.description}</p>
</div>
<div>
<ul>
<li className="badge badge-primary">
<FontAwesomeIcon icon={faStar} /> Stars: 42
</li>
<li className="badge badge-dark">
<FontAwesomeIcon icon={faEye} /> Watchers: 2
</li>
<li className="badge badge-light">
<FontAwesomeIcon icon={faCodeBranch} /> Forks: 4
</li>
</ul>
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default Profile;

27
src/pages/SignIn.tsx Normal file
View file

@ -0,0 +1,27 @@
import React, {FC} from 'react';
import Header from '../components/Header';
/**
* Sign in form
*/
const SignIn: FC = () => (
<section className="container">
<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" />
</form>
<p className="my-1">
Don't have an account? <a href="register.html">Sign in</a>
</p>
</section>
);
export default SignIn;

34
src/pages/SignUp.tsx Normal file
View file

@ -0,0 +1,34 @@
import React, {FC} from 'react';
import Header from '../components/Header';
/**
* Sign up form
*/
const SignUp: FC = () => (
<section className="container">
<Header title="Sign Up" lead="Create your account" />
<form action="dashboard.html" className="form">
<div className="form-group">
<input type="text" placeholder="Name" required />
</div>
<div className="form-group">
<input type="email" placeholder="Email Address" />
<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? <a href="login.html">Sign in</a>
</p>
</section>
);
export default SignUp;

33
src/router/Router.tsx Normal file
View file

@ -0,0 +1,33 @@
import React, {FC} from 'react';
import {Switch, Route} from 'react-router-dom';
import Landing from '../pages/Landing';
import SignUp from '../pages/SignUp';
import SignIn from '../pages/SignIn';
import Developers from '../pages/Developers';
import Profile from '../pages/Profile';
import EditProfile from '../pages/EditProfile';
import Dashboard from '../pages/Dashboard';
import AddExperience from '../pages/AddExperience';
import AddEducation from '../pages/AddEducation';
import PostPage from '../pages/Post';
import Posts from '../pages/Posts';
import * as ROUTES from '../constants/routes';
/** Register navigation paths accessible */
const Router: FC = () => (
<Switch>
<Route exact path={ROUTES.LANDING} component={Landing} />
<Route exact path={ROUTES.SIGN_UP} component={SignUp} />
<Route exact path={ROUTES.SIGN_IN} component={SignIn} />
<Route exact path={ROUTES.DEVELOPERS} component={Developers} />
<Route exact path={ROUTES.PROFILE} component={Profile} />
<Route exact path={ROUTES.EDIT_PROFILE} component={EditProfile} />
<Route exact path={ROUTES.DASHBOARD} component={Dashboard} />
<Route exact path={ROUTES.ADD_EXPERIENCE} component={AddExperience} />
<Route exact path={ROUTES.ADD_EDUCATION} component={AddEducation} />
<Route exact path={ROUTES.POST} component={PostPage} />
<Route exact path={ROUTES.POSTS} component={Posts} />
</Switch>
);
export default Router;

View file

@ -316,23 +316,23 @@
.form .social-input {
display: flex;
}
.form .social-input i {
.form .social-input svg {
padding: 0.5rem;
width: 4rem;
width: 3rem;
}
.form .social-input i.fa-twitter {
.form .social-input svg.fa-twitter {
color: #38a1f3;
}
.form .social-input i.fa-facebook {
.form .social-input svg.fa-facebook {
color: #3b5998;
}
.form .social-input i.fa-instagram {
.form .social-input svg.fa-instagram {
color: #3f729b;
}
.form .social-input i.fa-youtube {
.form .social-input svg.fa-youtube {
color: #c4302b;
}
.form .social-input i.fa-linkedin {
.form .social-input svg.fa-linkedin {
color: #0077b5;
}
@media (max-width: 700px) {

View file

@ -7,10 +7,10 @@
margin-top: 0.3rem;
color: #888;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="date"],
input[type='text'],
input[type='email'],
input[type='password'],
input[type='date'],
select,
textarea {
display: block;
@ -21,16 +21,16 @@
border-radius: 0.3rem;
}
input[type="submit"] {
input[type='submit'] {
font: inherit;
}
.social-input {
display: flex;
i {
svg {
padding: 0.5rem;
width: 4rem;
width: 3rem;
&.fa-twitter {
color: #38a1f3;

9
src/types/Comment.ts Normal file
View file

@ -0,0 +1,9 @@
interface Comment {
// userID: string;
text: string;
name: string;
picture: string;
// date: Date;
}
export default Comment;

12
src/types/Education.ts Normal file
View file

@ -0,0 +1,12 @@
import TimePeriod from '../types/TimePeriod';
interface Education {
school: string;
from: TimePeriod;
to: TimePeriod;
degree: string;
field: string;
description: string;
}
export default Education;

11
src/types/Experience.ts Normal file
View file

@ -0,0 +1,11 @@
import TimePeriod from '../types/TimePeriod';
interface Experience {
company: string;
from: Date;
to: TimePeriod;
position: string;
description: string;
}
export default Experience;

10
src/types/Repo.ts Normal file
View file

@ -0,0 +1,10 @@
interface Repo {
name: string;
description: string;
link: string;
stars: number;
watchers: number;
forks: number;
}
export default Repo;

21
src/types/TimePeriod.ts Normal file
View file

@ -0,0 +1,21 @@
import moment from 'moment';
type TimePeriod = Date | 'Current';
/** format exp date to be used */
const parseDate = (date: TimePeriod): string => {
if (date === 'Current') {
return date;
}
return moment(date).format('MMM. YYYY');
};
/**
* Formats a time period assignment: experience or education.
* @param from Start of the assignment. Must be a Date
* @param to End of the assignment. Can be "Current"
*/
export const getTimePeriod = (from: TimePeriod, to: TimePeriod): string =>
`${parseDate(from)} - ${parseDate(to)}`;
export default TimePeriod;

121
yarn.lock
View file

@ -956,7 +956,7 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.5.1", "@babel/runtime@^7.7.4":
"@babel/runtime@^7.1.2", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.4":
version "7.9.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
@ -1572,6 +1572,11 @@
"@types/minimatch" "*"
"@types/node" "*"
"@types/history@*":
version "4.7.5"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860"
integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
@ -1665,6 +1670,23 @@
dependencies:
"@types/react" "*"
"@types/react-router-dom@^5.1.5":
version "5.1.5"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
version "5.1.7"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.7.tgz#e9d12ed7dcfc79187e4d36667745b69a5aa11556"
integrity sha512-2ouP76VQafKjtuc0ShpwUebhHwJo0G6rhahW9Pb8au3tQTjYXd2jta4wv6U2tGLR/I42yuG00+UXjNYY0dTzbg==
dependencies:
"@types/history" "*"
"@types/react" "*"
"@types/react@*", "@types/react@^16.9.0":
version "16.9.35"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.35.tgz#a0830d172e8aadd9bd41709ba2281a3124bbd368"
@ -5295,6 +5317,18 @@ hex-color-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
history@^4.9.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
dependencies:
"@babel/runtime" "^7.1.2"
loose-envify "^1.2.0"
resolve-pathname "^3.0.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
value-equal "^1.0.1"
hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
@ -5304,6 +5338,13 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.1.0:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
hosted-git-info@^2.1.4:
version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
@ -6010,6 +6051,11 @@ is-wsl@^2.1.1:
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@ -6939,7 +6985,7 @@ loglevel@^1.6.6:
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56"
integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -7148,6 +7194,14 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256"
integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=
mini-create-react-context@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==
dependencies:
"@babel/runtime" "^7.5.5"
tiny-warning "^1.0.3"
mini-css-extract-plugin@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
@ -7271,6 +7325,11 @@ moment@2.24.0:
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
moment@^2.25.3:
version "2.25.3"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.25.3.tgz#252ff41319cf41e47761a1a88cab30edfe9808c0"
integrity sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@ -7961,6 +8020,13 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@ -9061,11 +9127,40 @@ react-error-overlay@^6.0.7:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"
integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA==
react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
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:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-router-dom@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-router "5.2.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
mini-create-react-context "^0.4.0"
path-to-regexp "^1.7.0"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-scripts@3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a"
@ -9431,6 +9526,11 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-pathname@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
resolve-url-loader@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0"
@ -10488,6 +10588,16 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-invariant@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tmp@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877"
@ -10845,6 +10955,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"