refactor: Profile folder

This commit is contained in:
Ruidy Nemausat 2020-06-02 22:37:02 +02:00
parent 1512e1a20f
commit 829668c00f
10 changed files with 288 additions and 239 deletions

View file

@ -1,239 +0,0 @@
import React, {FC} from 'react';
// Redux
import {compose} from '@reduxjs/toolkit';
import {firestoreConnect} from 'react-redux-firebase';
import {connect} from 'react-redux';
import {RootState} from '../store';
// Routing
import {Link, useParams} from 'react-router-dom';
import Routes from '../constants/routes';
import NotFound from './NotFound';
// Style
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faGithub,
faFacebook,
faInstagram,
faLinkedin,
faTwitter,
faYoutube,
} from '@fortawesome/free-brands-svg-icons';
import {
faGlobe,
IconDefinition,
faCheck,
faStar,
faEye,
faCodeBranch,
} from '@fortawesome/free-solid-svg-icons';
// Typing
import IDev, {getDescription} from '../models/Dev';
import Experience from '../types/Experience';
import {getTimePeriod} from '../types/TimePeriod';
import Education from '../types/Education';
import Repo from '../types/Repo';
interface IProps {
dev: IDev;
}
/**
* Dev personal profile as seen by other people.
*/
const Profile: FC<IProps> = ({dev}) => {
// display 404 page if dev is null
if (dev === null) {
return <NotFound />;
}
/** 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;
case 'youtube':
return faYoutube;
default:
return faGlobe;
}
};
return dev === undefined ? (
<div>Loading ... </div>
) : (
<section className="container">
<Link to={Routes.DEVELOPERS} className="btn">
Back to profiles
</Link>
<div className="profile-grid my-1">
<div className="profile-top bg-primary p-2">
<img
src={dev.avatarUrl}
alt={dev.displayName}
className="round-img my-1"
/>
<h1 className="large">{dev.displayName}</h1>
<p className="lead">{getDescription(dev.status, dev.company)}</p>
<p>{dev.location}</p>
<div className="icons my-1">
{Object.entries(dev.links)
.sort()
.map(([icon, webAddress], i: number) => (
<a
href={webAddress}
key={i}
target="_blank"
rel="noopener noreferrer"
>
<FontAwesomeIcon icon={renderSocialIcon(icon)} size="2x" />
</a>
))}
</div>
</div>
<div className="profile-about bg-light p-2">
<h2 className="text-primary">{`${dev.displayName}'s Bio`}</h2>
<p>
{dev.bio.length === 0
? 'Add a short bio to present yourself!'
: dev.bio}
</p>
<div className="line"></div>
<h2 className="text-primary">Skill Set</h2>
<div className="skills">
{dev.skills.length === 0
? 'Let us know about your 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.length === 0 ? (
<div>
<img
src={require('../static/img/404.jpg')}
alt="no experiences"
/>
</div>
) : (
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.length === 0 ? (
<div>
<img src={require('../static/img/404.jpg')} alt="no educations" />
</div>
) : (
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?.length === 0 ? (
<div>
<img
src={require('../static/img/404.jpg')}
alt="no repositories"
/>
</div>
) : (
dev.repos.map((r: Repo, i: number) => (
<div className="repo bg-white my-1 p-1" key={i}>
<div>
<h4>
<a href={r.url} target="_blank" rel="noopener noreferrer">
{r.name}
</a>
</h4>
<p>{r.description}</p>
</div>
<div>
<ul>
<li className="badge badge-primary">
<FontAwesomeIcon icon={faStar} /> Stars: {r.stars}
</li>
<li className="badge badge-dark">
<FontAwesomeIcon icon={faEye} /> Watchers: {r.watchers}
</li>
<li className="badge badge-light">
<FontAwesomeIcon icon={faCodeBranch} /> Forks: {r.forks}
</li>
</ul>
</div>
</div>
))
)}
</div>
</div>
</section>
);
};
/**
* Container to fetch id params from thr URI and pass it to Profile page
*/
const ProfileContainer: FC = () => {
const {id} = useParams();
const Component = compose<FC>(
firestoreConnect(() => [`users/${id}`]),
connect(({firestore: {data}}: RootState) => ({
dev: data.users && data.users[id],
})),
)(Profile);
return <Component />;
};
export default ProfileContainer;

View file

@ -0,0 +1,26 @@
import React, {FC} from 'react';
// Styling
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCheck} from '@fortawesome/free-solid-svg-icons';
import IDev from '../../models/Dev';
const ProfileAbout: FC<IDev> = ({displayName, bio, skills}) => (
<div className="profile-about bg-light p-2">
<h2 className="text-primary">{`${displayName}'s Bio`}</h2>
<p>{bio.length === 0 ? 'Add a short bio to present yourself!' : bio}</p>
<div className="line"></div>
<h2 className="text-primary">Skill Set</h2>
<div className="skills">
{skills.length === 0
? 'Let us know about your skills!'
: skills?.map((s: string, i: number) => (
<div className="p-1" key={i}>
<FontAwesomeIcon icon={faCheck} /> {s}
</div>
))}
</div>
</div>
);
export default React.memo(ProfileAbout);

View file

@ -0,0 +1,38 @@
import React, {FC} from 'react';
import Education from '../../types/Education';
import {getTimePeriod} from '../../types/TimePeriod';
import educationPicture from '../../static/img/education.svg';
const ProfileEducation: FC<{educations: Education[]}> = ({educations}) => (
<div className="profile-edu bg-white p-2">
<h2 className="text-primary">Education</h2>
{educations.length === 0 ? (
<div>
<img src={educationPicture} alt="no educations" />
</div>
) : (
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>
);
export default React.memo(ProfileEducation);

View file

@ -0,0 +1,34 @@
import React, {FC} from 'react';
import Experience from '../../types/Experience';
import {getTimePeriod} from '../../types/TimePeriod';
import expPicture from '../../static/img/experience.svg';
const ProfileExperience: FC<{experiences: Experience[]}> = ({experiences}) => (
<div className="profile-exp bg-white p-2">
<h2 className="text-primary">Experiences</h2>
{experiences.length === 0 ? (
<div>
<img src={expPicture} alt="no experiences" />
</div>
) : (
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>
);
export default ProfileExperience;

View file

@ -0,0 +1,50 @@
import React, {FC} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faGithub} from '@fortawesome/free-brands-svg-icons';
import {faStar, faEye, faCodeBranch} from '@fortawesome/free-solid-svg-icons';
import Repo from '../../types/Repo';
import githubPicture from '../../static/img/github.svg';
const ProfileGithub: FC<{repos: Repo[]}> = ({repos}) => (
<div className="profile-github">
<h2 className="text-primary my-1">
<FontAwesomeIcon icon={faGithub} /> GitHub Repos
</h2>
{repos?.length === 0 ? (
<div>
<img src={githubPicture} alt="no repositories" />
</div>
) : (
repos.map((r: Repo, i: number) => (
<div className="repo bg-white my-1 p-1" key={i}>
<div>
<h4>
<a href={r.url} target="_blank" rel="noopener noreferrer">
{r.name}
</a>
</h4>
<p>{r.description}</p>
</div>
<div>
<ul>
<li className="badge badge-primary">
<FontAwesomeIcon icon={faStar} /> Stars: {r.stars}
</li>
<li className="badge badge-dark">
<FontAwesomeIcon icon={faEye} /> Watchers: {r.watchers}
</li>
<li className="badge badge-light">
<FontAwesomeIcon icon={faCodeBranch} /> Forks: {r.forks}
</li>
</ul>
</div>
</div>
))
)}
</div>
);
export default React.memo(ProfileGithub);

68
src/pages/Profile/Top.tsx Normal file
View file

@ -0,0 +1,68 @@
import React, {FC} from 'react';
// Styling
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {IconDefinition} from '@fortawesome/fontawesome-svg-core';
import {
faFacebook,
faGithub,
faInstagram,
faLinkedin,
faTwitter,
faYoutube,
} from '@fortawesome/free-brands-svg-icons';
import {faGlobe} from '@fortawesome/free-solid-svg-icons';
import IDev, {getDescription} from '../../models/Dev';
const ProfileTop: FC<IDev> = ({
avatarUrl,
displayName,
status,
company,
links,
location,
}) => {
/** 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;
case 'youtube':
return faYoutube;
default:
return faGlobe;
}
};
return (
<div className="profile-top bg-primary p-2">
<img src={avatarUrl} alt={displayName} className="round-img my-1" />
<h1 className="large">{displayName}</h1>
<p className="lead">{getDescription(status, company)}</p>
<p>{location}</p>
<div className="icons my-1">
{Object.entries(links)
.sort()
.map(([icon, webAddress], i: number) => (
<a
href={webAddress}
key={i}
target="_blank"
rel="noopener noreferrer"
>
<FontAwesomeIcon icon={renderSocialIcon(icon)} size="2x" />
</a>
))}
</div>
</div>
);
};
export default React.memo(ProfileTop);

View file

@ -0,0 +1,69 @@
import React, {FC} from 'react';
// Redux
import {compose} from '@reduxjs/toolkit';
import {firestoreConnect} from 'react-redux-firebase';
import {connect} from 'react-redux';
import {RootState} from '../../store';
// Routing
import {Link, useParams} from 'react-router-dom';
import Routes from '../../constants/routes';
import NotFound from '../NotFound';
// Typing
import IDev from '../../models/Dev';
import ProfileTop from './Top';
import ProfileAbout from './About';
import ProfileExperience from './Experience';
import ProfileEducation from './Education';
import ProfileGithub from './Github';
import Collections from '../../constants/collections';
interface IProps {
dev: IDev;
}
/**
* Dev personal profile as seen by other people.
*/
const ProfileComponent: FC<IProps> = ({dev}) => {
// display 404 page if dev is null
if (dev === null) {
return <NotFound />;
}
return dev === undefined ? (
<div>Loading ... </div>
) : (
<section className="container">
<Link to={Routes.DEVELOPERS} className="btn">
Back to profiles
</Link>
<div className="profile-grid my-1">
<ProfileTop {...dev} />
<ProfileAbout {...dev} />
<ProfileExperience experiences={dev.experiences} />
<ProfileEducation educations={dev.educations} />
<ProfileGithub repos={dev.repos} />
</div>
</section>
);
};
/**
* Container to fetch id params from thr URI and pass it to Profile page
*/
const Profile: FC = () => {
const {id} = useParams();
const Component = compose<FC>(
firestoreConnect(() => [`${Collections.USERS}/${id}`]),
connect(({firestore: {data}}: RootState) => ({
dev: data.users && data.users[id],
})),
)(ProfileComponent);
return <Component />;
};
export default Profile;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB