mirror of
https://github.com/rjNemo/devbook_ts
synced 2026-06-06 02:36:39 +00:00
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:
parent
9e59bb0f2c
commit
cdba48cc72
31 changed files with 1352 additions and 58 deletions
6
cypress/integration/layout.spec.js
Normal file
6
cypress/integration/layout.spec.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
describe('App Layout', () => {
|
||||
it('contains a navbar', () => {
|
||||
cy.visit('/');
|
||||
cy.get('nav');
|
||||
});
|
||||
});
|
||||
58
cypress/integration/router.spec.js
Normal file
58
cypress/integration/router.spec.js
Normal 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');
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
50
src/App.tsx
50
src/App.tsx
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
38
src/components/DevProfile.tsx
Normal file
38
src/components/DevProfile.tsx
Normal 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;
|
||||
18
src/components/FormHeader.tsx
Normal file
18
src/components/FormHeader.tsx
Normal 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
48
src/components/Header.tsx
Normal 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
29
src/components/NavBar.tsx
Normal 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
15
src/constants/routes.ts
Normal 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
106
src/models/Dev.ts
Normal 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
47
src/models/Post.ts
Normal 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;
|
||||
65
src/pages/AddEducation.tsx
Normal file
65
src/pages/AddEducation.tsx
Normal 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;
|
||||
57
src/pages/AddExperience.tsx
Normal file
57
src/pages/AddExperience.tsx
Normal 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
90
src/pages/Dashboard.tsx
Normal 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
49
src/pages/Developers.tsx
Normal 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
121
src/pages/EditProfile.tsx
Normal 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
28
src/pages/Landing.tsx
Normal 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
59
src/pages/Post.tsx
Normal 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
58
src/pages/Posts.tsx
Normal 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
159
src/pages/Profile.tsx
Normal 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
27
src/pages/SignIn.tsx
Normal 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
34
src/pages/SignUp.tsx
Normal 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
33
src/router/Router.tsx
Normal 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;
|
||||
14
src/static/css/style.min.css
vendored
14
src/static/css/style.min.css
vendored
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
9
src/types/Comment.ts
Normal 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
12
src/types/Education.ts
Normal 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
11
src/types/Experience.ts
Normal 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
10
src/types/Repo.ts
Normal 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
21
src/types/TimePeriod.ts
Normal 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
121
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue