From cdba48cc728debf0b2505526ec23f93fd135fd49 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Tue, 12 May 2020 23:09:40 +0200 Subject: [PATCH] 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 --- cypress/integration/layout.spec.js | 6 ++ cypress/integration/router.spec.js | 58 +++++++++++ package.json | 3 + src/App.tsx | 50 ++------- src/components/DevProfile.tsx | 38 +++++++ src/components/FormHeader.tsx | 18 ++++ src/components/Header.tsx | 48 +++++++++ src/components/NavBar.tsx | 29 ++++++ src/constants/routes.ts | 15 +++ src/models/Dev.ts | 106 +++++++++++++++++++ src/models/Post.ts | 47 +++++++++ src/pages/AddEducation.tsx | 65 ++++++++++++ src/pages/AddExperience.tsx | 57 +++++++++++ src/pages/Dashboard.tsx | 90 ++++++++++++++++ src/pages/Developers.tsx | 49 +++++++++ src/pages/EditProfile.tsx | 121 ++++++++++++++++++++++ src/pages/Landing.tsx | 28 +++++ src/pages/Post.tsx | 59 +++++++++++ src/pages/Posts.tsx | 58 +++++++++++ src/pages/Profile.tsx | 159 +++++++++++++++++++++++++++++ src/pages/SignIn.tsx | 27 +++++ src/pages/SignUp.tsx | 34 ++++++ src/router/Router.tsx | 33 ++++++ src/static/css/style.min.css | 14 +-- src/static/scss/_form.scss | 14 +-- src/types/Comment.ts | 9 ++ src/types/Education.ts | 12 +++ src/types/Experience.ts | 11 ++ src/types/Repo.ts | 10 ++ src/types/TimePeriod.ts | 21 ++++ yarn.lock | 121 +++++++++++++++++++++- 31 files changed, 1352 insertions(+), 58 deletions(-) create mode 100644 cypress/integration/layout.spec.js create mode 100644 cypress/integration/router.spec.js create mode 100644 src/components/DevProfile.tsx create mode 100644 src/components/FormHeader.tsx create mode 100644 src/components/Header.tsx create mode 100644 src/components/NavBar.tsx create mode 100644 src/constants/routes.ts create mode 100644 src/models/Dev.ts create mode 100644 src/models/Post.ts create mode 100644 src/pages/AddEducation.tsx create mode 100644 src/pages/AddExperience.tsx create mode 100644 src/pages/Dashboard.tsx create mode 100644 src/pages/Developers.tsx create mode 100644 src/pages/EditProfile.tsx create mode 100644 src/pages/Landing.tsx create mode 100644 src/pages/Post.tsx create mode 100644 src/pages/Posts.tsx create mode 100644 src/pages/Profile.tsx create mode 100644 src/pages/SignIn.tsx create mode 100644 src/pages/SignUp.tsx create mode 100644 src/router/Router.tsx create mode 100644 src/types/Comment.ts create mode 100644 src/types/Education.ts create mode 100644 src/types/Experience.ts create mode 100644 src/types/Repo.ts create mode 100644 src/types/TimePeriod.ts diff --git a/cypress/integration/layout.spec.js b/cypress/integration/layout.spec.js new file mode 100644 index 0000000..28e6d62 --- /dev/null +++ b/cypress/integration/layout.spec.js @@ -0,0 +1,6 @@ +describe('App Layout', () => { + it('contains a navbar', () => { + cy.visit('/'); + cy.get('nav'); + }); +}); diff --git a/cypress/integration/router.spec.js b/cypress/integration/router.spec.js new file mode 100644 index 0000000..95f5121 --- /dev/null +++ b/cypress/integration/router.spec.js @@ -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'); + }); +}); diff --git a/package.json b/package.json index 7ece26f..29faf9a 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/App.tsx b/src/App.tsx index 701cf9e..82d9ffa 100644 --- a/src/App.tsx +++ b/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 ( - <> - -
-
-
-

DevBook

-

- Create developer profiles, portfolio, share and get help from - other devs -

-
- - Sign up - - - Login - -
-
-
-
- + + + + ); }; diff --git a/src/components/DevProfile.tsx b/src/components/DevProfile.tsx new file mode 100644 index 0000000..9c19076 --- /dev/null +++ b/src/components/DevProfile.tsx @@ -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 = ({ + id, + name, + picture, + description, + location, + skills, +}) => ( +
+ {name} +
+

{name}

+

{description}

+

{location}

+ + View Profile + +
+
    + {skills.map((s, i) => ( +
  • + {s} +
  • + ))} +
+
+); + +export default DevProfile; diff --git a/src/components/FormHeader.tsx b/src/components/FormHeader.tsx new file mode 100644 index 0000000..2a671e8 --- /dev/null +++ b/src/components/FormHeader.tsx @@ -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 = props => ( + <> +
+ * marks required fields + +); + +export default FormHeader; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..bcfb935 --- /dev/null +++ b/src/components/Header.tsx @@ -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 = ({title, lead, icon = 'faUser'}) => { + const RenderIcon = (icon: string) => { + if (icon === 'faUser') { + return ; + } + if (icon === 'connectdevelop') { + return ; + } + if (icon === 'code-branch') { + return ; + } + if (icon === 'graduation-cap') { + return ; + } + }; + + return ( + <> +

{title}

+

+ {RenderIcon(icon)} {lead} +

+ + ); +}; + +export default Header; diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx new file mode 100644 index 0000000..a88a8f8 --- /dev/null +++ b/src/components/NavBar.tsx @@ -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 = () => ( + +); + +export default NavBar; diff --git a/src/constants/routes.ts b/src/constants/routes.ts new file mode 100644 index 0000000..33e8207 --- /dev/null +++ b/src/constants/routes.ts @@ -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'; diff --git a/src/models/Dev.ts b/src/models/Dev.ts new file mode 100644 index 0000000..131f025 --- /dev/null +++ b/src/models/Dev.ts @@ -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; diff --git a/src/models/Post.ts b/src/models/Post.ts new file mode 100644 index 0000000..ffa7d71 --- /dev/null +++ b/src/models/Post.ts @@ -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; diff --git a/src/pages/AddEducation.tsx b/src/pages/AddEducation.tsx new file mode 100644 index 0000000..0ad839b --- /dev/null +++ b/src/pages/AddEducation.tsx @@ -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 = () => ( +
+ + +
+
+ +
+
+ +
+
+ +
+
+

From Date

+ +
+
+

To Date

+ +
+
+

+ Current School +

+
+
+ +
+ + + Go Back + +
+
+); + +export default AddEducation; diff --git a/src/pages/AddExperience.tsx b/src/pages/AddExperience.tsx new file mode 100644 index 0000000..a8f18c3 --- /dev/null +++ b/src/pages/AddExperience.tsx @@ -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 ( +
+ + +
+
+ +
+
+ +
+
+ +
+
+

From Date

+ +
+
+

To Date

+ +
+
+

+ Current Job +

+
+
+ +
+ + + Go Back + +
+
+ ); +}; + +export default AddExperience; diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx new file mode 100644 index 0000000..bdb58c3 --- /dev/null +++ b/src/pages/Dashboard.tsx @@ -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 = () => { + return ( +
+
+ + +

Experience Credentials

+ + + + + + + + + + + {dev.experiences.map((exp: Experience, i: number) => ( + + + + + + + ))} + +
CompanyTitleYears
{exp.company}{exp.position}{getTimePeriod(exp.from, exp.to)} + +
+ +

Education Credentials

+ + + + + + + + + + + {dev.educations.map((edu: Education, i: number) => ( + + + + + + + ))} + +
SchoolDegreeYears
{edu.school}{edu.field}{getTimePeriod(edu.from, edu.to)} + +
+
+ +
+
+ ); +}; + +export default Dashboard; diff --git a/src/pages/Developers.tsx b/src/pages/Developers.tsx new file mode 100644 index 0000000..65c00e7 --- /dev/null +++ b/src/pages/Developers.tsx @@ -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 = (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 ( +
+
+
+ {developers.map(dev => ( + // use spread operator to pass props + + ))} +
+
+ ); +}; + +export default Developers; diff --git a/src/pages/EditProfile.tsx b/src/pages/EditProfile.tsx new file mode 100644 index 0000000..5243433 --- /dev/null +++ b/src/pages/EditProfile.tsx @@ -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 ( +
+ + +
+
+ + + Give us an idea of where you are at in your career + +
+
+ + + Could be your own company or one you work for + +
+
+ + + Could be your own or a company website + +
+
+ + + City & state suggested (eg. Boston, MA) + +
+
+ + + Please use comma separated values (eg. HTML,CSS,JavaScript,PHP) + +
+
+ + + If you want your latest repos and a Github link, include your + username + +
+
+ + Tell us a little about yourself +
+ +
+ + Optional +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + + Go Back + +
+
+ ); +}; + +export default EditProfile; diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx new file mode 100644 index 0000000..96b11e8 --- /dev/null +++ b/src/pages/Landing.tsx @@ -0,0 +1,28 @@ +import React, {FC} from 'react'; + +/** + * Landing page + */ +const Landing: FC = () => ( +
+
+
+

DevBook

+

+ Create developer profiles, portfolio, share and get help from other + devs +

+ +
+
+
+); + +export default Landing; diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx new file mode 100644 index 0000000..8ddf23b --- /dev/null +++ b/src/pages/Post.tsx @@ -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 = () => ( +
+ + Back To Posts + + +
+ +
+

{post.text}

+
+
+ +
+
+

Leave A Comment

+
+
+ + +
+
+ +
+ {post.comments.map((c: Comment, i: number) => ( +
+ +
+

{c.text}

+
+
+ ))} +
+
+); + +export default PostPage; diff --git a/src/pages/Posts.tsx b/src/pages/Posts.tsx new file mode 100644 index 0000000..e26d47f --- /dev/null +++ b/src/pages/Posts.tsx @@ -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 ( +
+
+
+
+

Say Something

+
+ +
+ + +
+ {posts.map((post: Post) => ( +
+ +
+

{post.text}

+ + + + Discussion + +
+
+ ))} +
+
+
+
+ ); +}; + +export default Posts; diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx new file mode 100644 index 0000000..5e07a6d --- /dev/null +++ b/src/pages/Profile.tsx @@ -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 = () => { + /** 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 ( +
+ + Back to profiles + + +
+
+ Some guy +

{dev.name}

+

{dev.description}

+

{dev.location}

+
+ {Object.entries(dev.links).map(([icon, webAddress], i: number) => ( + + + + ))} +
+
+ +
+

{`${dev.name}'s Bio`}

+

{dev.bio}

+
+

Skill Set

+
+ {dev.skills.map((s: string, i: number) => ( +
+ {s} +
+ ))} +
+
+ +
+

Experiences

+ {dev.experiences.map((exp: Experience, i: number) => ( +
+

{exp.company}

+

{getTimePeriod(exp.from, exp.to)}

+

+ Position: + {exp.position} +

+

+ Description: + {exp.description} +

+
+ ))} +
+ +
+

Education

+ {dev.educations.map((edu: Education, i: number) => ( +
+

{edu.school}

+

{getTimePeriod(edu.from, edu.to)}

+

+ Degree: + {edu.degree} +

+

+ Field: + {edu.field} +

+

+ Description: + {edu.description} +

+
+ ))} +
+ +
+

+ GitHub Repos +

+ + {dev.repos.map((r: Repo, i: number) => ( +
+
+

+ {r.name} +

+

{r.description}

+
+
+
    +
  • + Stars: 42 +
  • +
  • + Watchers: 2 +
  • +
  • + Forks: 4 +
  • +
+
+
+ ))} +
+
+
+ ); +}; + +export default Profile; diff --git a/src/pages/SignIn.tsx b/src/pages/SignIn.tsx new file mode 100644 index 0000000..abc5ac6 --- /dev/null +++ b/src/pages/SignIn.tsx @@ -0,0 +1,27 @@ +import React, {FC} from 'react'; +import Header from '../components/Header'; + +/** + * Sign in form + */ +const SignIn: FC = () => ( +
+
Invalid credentials
+
+
+
+ +
+
+ +
+ + +
+

+ Don't have an account? Sign in +

+
+); + +export default SignIn; diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx new file mode 100644 index 0000000..ef7ff86 --- /dev/null +++ b/src/pages/SignUp.tsx @@ -0,0 +1,34 @@ +import React, {FC} from 'react'; +import Header from '../components/Header'; + +/** + * Sign up form + */ +const SignUp: FC = () => ( +
+
+
+
+ +
+
+ + + This site uses Gravatar, so use a Gravatar email. + +
+
+ +
+
+ +
+ +
+

+ Already have an account? Sign in +

+
+); + +export default SignUp; diff --git a/src/router/Router.tsx b/src/router/Router.tsx new file mode 100644 index 0000000..306d4af --- /dev/null +++ b/src/router/Router.tsx @@ -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 = () => ( + + + + + + + + + + + + + +); + +export default Router; diff --git a/src/static/css/style.min.css b/src/static/css/style.min.css index 306ad5b..5ef764d 100644 --- a/src/static/css/style.min.css +++ b/src/static/css/style.min.css @@ -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) { diff --git a/src/static/scss/_form.scss b/src/static/scss/_form.scss index 2989979..9b0237e 100644 --- a/src/static/scss/_form.scss +++ b/src/static/scss/_form.scss @@ -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; diff --git a/src/types/Comment.ts b/src/types/Comment.ts new file mode 100644 index 0000000..e627d7b --- /dev/null +++ b/src/types/Comment.ts @@ -0,0 +1,9 @@ +interface Comment { + // userID: string; + text: string; + name: string; + picture: string; + // date: Date; +} + +export default Comment; diff --git a/src/types/Education.ts b/src/types/Education.ts new file mode 100644 index 0000000..fcd6d3c --- /dev/null +++ b/src/types/Education.ts @@ -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; diff --git a/src/types/Experience.ts b/src/types/Experience.ts new file mode 100644 index 0000000..face0b0 --- /dev/null +++ b/src/types/Experience.ts @@ -0,0 +1,11 @@ +import TimePeriod from '../types/TimePeriod'; + +interface Experience { + company: string; + from: Date; + to: TimePeriod; + position: string; + description: string; +} + +export default Experience; diff --git a/src/types/Repo.ts b/src/types/Repo.ts new file mode 100644 index 0000000..c423b13 --- /dev/null +++ b/src/types/Repo.ts @@ -0,0 +1,10 @@ +interface Repo { + name: string; + description: string; + link: string; + stars: number; + watchers: number; + forks: number; +} + +export default Repo; diff --git a/src/types/TimePeriod.ts b/src/types/TimePeriod.ts new file mode 100644 index 0000000..f78ce40 --- /dev/null +++ b/src/types/TimePeriod.ts @@ -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; diff --git a/yarn.lock b/yarn.lock index 7ded3c0..c9841d5 100644 --- a/yarn.lock +++ b/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"