Skip to content

Commit

Permalink
Hook up apis and implement scroll behavior of project details
Browse files Browse the repository at this point in the history
  • Loading branch information
jamessspanggg committed Apr 25, 2021
1 parent 74d1351 commit 2051dcf
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 105 deletions.
8 changes: 8 additions & 0 deletions src/apis/projects/api.ts
Expand Up @@ -29,3 +29,11 @@ export async function getMilestonesByProject(id: string): Promise<Milestone[]> {
}
return response.data.results;
}

export async function getProjectMemberById(id: string) {
const response = await axios.get(`${process.env.REACT_APP_BACKEND_API}/project_members/${id}`);
if (!response.data) {
throw new Error('Error while fetching project member, please try again.');
}
return response.data;
}
9 changes: 9 additions & 0 deletions src/apis/users/index.ts
@@ -1,6 +1,7 @@
import axios from 'axios';

import {standardHeaders} from 'utils/requests';
import {User} from 'types/app/User';

export async function createUser({
display_name,
Expand All @@ -13,3 +14,11 @@ export async function createUser({
}) {
return axios.post(`${process.env.REACT_APP_BACKEND_API}/users`, {display_name, email, password}, standardHeaders());
}

export async function getUser({uuid}: {uuid: string}): Promise<User> {
const response = await axios.get<User>(`${process.env.REACT_APP_BACKEND_API}/users/${uuid}`);
if (!response.data) {
throw new Error('Error while fetching user. Please try again.');
}
return response.data;
}
23 changes: 9 additions & 14 deletions src/containers/Projects/ListOfProjects/index.tsx
@@ -1,32 +1,27 @@
import React, {FC} from 'react';

import {Project} from 'types/projects';
import ProjectCard from '../ProjectCard';

import './ListOfProjects.scss';

const sampleProject = {
description:
'Lorem ipsum dolor sit amet, consecteturadia adipiscing elit, sed do eiusmod tempor incididunt ut.ed do eiusmod tempor incididunt ut.',
logoUrl: 'https://yt3.ggpht.com/ytc/AAUvwnglVjQeNSAVO9GgKkrjIbCO_y0rOx7Yxx-2bv9r_A=s176-c-k-c0x00ffffff-no-rj',
projectLead: 'Skylar',
title: 'Keysign',
type Props = {
projects: Project[];
};

const projects = [sampleProject, sampleProject, sampleProject, sampleProject, sampleProject, sampleProject];

const ListOfProjects: FC = () => {
const ListOfProjects: FC<Props> = ({projects}) => {
return (
<div className="ListOfProjects">
{projects.map((project, i) => {
const {description, logoUrl, projectLead, title} = project;
{projects.map((project) => {
const {description, logo, project_lead: projectLead, title, uuid} = project;
return (
<ProjectCard
description={description}
id={i.toString()}
logoUrl={logoUrl}
id={uuid}
logoUrl={logo}
projectLead={projectLead}
title={title}
key={i}
key={uuid}
/>
);
})}
Expand Down
19 changes: 17 additions & 2 deletions src/containers/Projects/ProjectCard/index.tsx
@@ -1,8 +1,11 @@
import React, {FC} from 'react';
import React, {FC, useEffect, useState} from 'react';

import {getUser} from 'apis/users';
import {api as projectApi} from 'apis/projects';
import {useHistory} from 'react-router';
import {Avatar} from 'components';
import Button from 'components/Button';
import {User} from 'types/app/User';
import {Icon, IconType} from '@thenewboston/ui';

import './ProjectCard.scss';
Expand All @@ -16,6 +19,18 @@ type Props = {
};

const ProjectCard: FC<Props> = ({description, id, logoUrl, projectLead, title}) => {
const [projectLeadUser, setProjectLeadUser] = useState<User | null>(null);

useEffect(() => {
(async function () {
// We require to call two extra APIs just to get the project lead's name, perhaps we
// should adopt a BFF design
const projectMemberResponse = await projectApi.getProjectMemberById(projectLead);
const userResponse = await getUser({uuid: projectMemberResponse.user});
setProjectLeadUser(userResponse);
})();
}, [projectLead]);

const history = useHistory();
return (
<div className="ProjectCard">
Expand All @@ -25,7 +40,7 @@ const ProjectCard: FC<Props> = ({description, id, logoUrl, projectLead, title})
<h1 className="ProjectCard__project-title">{title}</h1>
<div className="ProjectCard__project-lead-container">
<h4 className="ProjectCard__project-lead">Project Lead: </h4>
<h4 className="ProjectCard__project-lead-name">{projectLead}</h4>
<h4 className="ProjectCard__project-lead-name">{projectLeadUser?.display_name}</h4>
</div>
</div>
</div>
Expand Down
@@ -1,16 +1,32 @@
import React, {FC} from 'react';
import React, {FC, useEffect} from 'react';

import {Milestone, Project} from 'types/projects';
import {Milestone, Project, ProjectTopic, ProjectTopicTitle} from 'types/projects';
import ProjectDetailsTopic from '../ProjectDetailsTopic';
import {projectDetailsTopic} from '../constants';
import './ProjectDetailsContent.scss';

type Props = {
milestones: Milestone[];
project: Project;
currentTopic: ProjectTopic;
};

const ProjectDetailsContent: FC<Props> = ({milestones, project}) => {
const TOP_NAV_HEIGHT = 60;

const ProjectDetailsContent: FC<Props> = ({milestones, project, currentTopic}) => {
useEffect(() => {
const element = document.getElementById(currentTopic.anchor);
const scrollBehavior = element ? 'smooth' : 'auto';

// scroll to top if it is the first topic, else scroll directly to the topic
const top = currentTopic.title === ProjectTopicTitle.Overview || !element ? 0 : element.offsetTop - TOP_NAV_HEIGHT;

window.scrollTo({
behavior: scrollBehavior,
top,
});
}, [currentTopic]);

const renderMilestones = () => {
return milestones.map((milestone) => {
return (
Expand All @@ -24,42 +40,49 @@ const ProjectDetailsContent: FC<Props> = ({milestones, project}) => {
return (
<div className="ProjectDetailsContent">
<ProjectDetailsTopic
id={projectDetailsTopic.overview.anchor}
content={project.overview}
iconType={projectDetailsTopic.overview.iconType}
overview={projectDetailsTopic.overview.overview}
title={projectDetailsTopic.overview.title}
/>
<ProjectDetailsTopic
id={projectDetailsTopic.problem.anchor}
content={project.problem}
iconType={projectDetailsTopic.problem.iconType}
overview={projectDetailsTopic.problem.overview}
title={projectDetailsTopic.problem.title}
/>
<ProjectDetailsTopic
id={projectDetailsTopic.target_market.anchor}
content={project.target_market}
iconType={projectDetailsTopic.target_market.iconType}
overview={projectDetailsTopic.target_market.overview}
title={projectDetailsTopic.target_market.title}
/>
<ProjectDetailsTopic
id={projectDetailsTopic.benefits.anchor}
content={project.benefits}
iconType={projectDetailsTopic.benefits.iconType}
overview={projectDetailsTopic.benefits.overview}
title={projectDetailsTopic.benefits.title}
/>
<ProjectDetailsTopic
id={projectDetailsTopic.centered_around_tnb.anchor}
content={project.centered_around_tnb}
iconType={projectDetailsTopic.centered_around_tnb.iconType}
overview={projectDetailsTopic.centered_around_tnb.overview}
title={projectDetailsTopic.centered_around_tnb.title}
/>
<ProjectDetailsTopic
id={projectDetailsTopic.estimated_completion_date.anchor}
content={project.estimated_completion_date}
iconType={projectDetailsTopic.estimated_completion_date.iconType}
overview={projectDetailsTopic.estimated_completion_date.overview}
title={projectDetailsTopic.estimated_completion_date.title}
/>
<ProjectDetailsTopic
id={projectDetailsTopic.roadmap.anchor}
content={renderMilestones()}
iconType={projectDetailsTopic.roadmap.iconType}
overview={projectDetailsTopic.roadmap.overview}
Expand Down
Expand Up @@ -3,10 +3,11 @@
background-color: var(--color-white);
display: flex;
flex-direction: row;
padding: 47px 0;
padding: 47px;

@media (max-width: 480px) {
align-items: flex-start;
padding: 24px;
}

&__left-container {
Expand Down Expand Up @@ -37,11 +38,6 @@
color: var(--color-sail-gray-300);
}

&__project-lead-container {
display: flex;
gap: 5px;
}

&__project-lead-name {
color: var(--color-sail-gray-700);
}
Expand Down
@@ -1,8 +1,11 @@
import React, {FC} from 'react';
import React, {FC, useEffect, useState} from 'react';

import {getUser} from 'apis/users';
import {api as projectApi} from 'apis/projects';
import {Avatar} from 'components';
import Button from 'components/Button';
import {Icon, IconType} from '@thenewboston/ui';
import {User} from 'types/app/User';
import './ProjectDetailsHeader.scss';

type Props = {
Expand All @@ -13,6 +16,16 @@ type Props = {
};

const ProjectDetailsHeader: FC<Props> = ({github, logoUrl, projectLead, title}) => {
const [projectLeadUser, setProjectLeadUser] = useState<User | null>(null);
useEffect(() => {
(async function () {
// We require to call two extra APIs just to get the project lead's name, perhaps we
// should adopt a BFF design
const projectMemberResponse = await projectApi.getProjectMemberById(projectLead);
const userResponse = await getUser({uuid: projectMemberResponse.user});
setProjectLeadUser(userResponse);
})();
}, [projectLead]);
return (
<div className="ProjectDetailsHeader">
<Avatar src={logoUrl} size={64} />
Expand All @@ -21,8 +34,8 @@ const ProjectDetailsHeader: FC<Props> = ({github, logoUrl, projectLead, title})
<div className="ProjectDetailsHeader__title-container">
<h1 className="ProjectDetailsHeader__project-title">{title}</h1>
<div className="ProjectDetailsHeader__project-lead-container">
<h4 className="ProjectDetailsHeader__project-lead">Project Lead: </h4>
<h4 className="ProjectDetailsHeader__project-lead-name">{projectLead}</h4>
<span className="ProjectDetailsHeader__project-lead">Project Lead: </span>
<span className="ProjectDetailsHeader__project-lead-name">{projectLeadUser?.display_name}</span>
</div>
</div>
</div>
Expand Down
Expand Up @@ -2,6 +2,7 @@
background-color: var(--color-white);
border-radius: 0px 0px 0px 12px;
box-shadow: 0px 8px 88px rgba(0, 0, 0, 0.05);
flex-shrink: 0;
height: fit-content;
padding: 32px;
width: 438px;
Expand Down
Expand Up @@ -2,14 +2,19 @@ import React, {FC, useState} from 'react';

import clsx from 'clsx';

import {ProjectTopic} from 'types/projects';
import {Icon} from '@thenewboston/ui';
import {useWindowDimensions} from 'hooks';
import {projectDetailsTopic} from '../constants';

import './ProjectDetailsSideMenu.scss';

const ProjectDetailsSideMenu: FC = () => {
const [selectedTopicTitle, setSelectedTopicTitle] = useState<string>(projectDetailsTopic.overview.title);
type Props = {
currentTopic: ProjectTopic;
onClick: (topic: ProjectTopic) => void;
};

const ProjectDetailsSideMenu: FC<Props> = ({currentTopic, onClick}) => {
const [hoveredTopicTitle, setHoveredTopicTitle] = useState<string>('');

const {width} = useWindowDimensions();
Expand All @@ -28,12 +33,12 @@ const ProjectDetailsSideMenu: FC = () => {
return (
<div
className={clsx('ProjectDetailsSideMenu__topic', {
'ProjectDetailsSideMenu__topic--active': selectedTopicTitle === title,
'ProjectDetailsSideMenu__topic--active': currentTopic.title === title,
'ProjectDetailsSideMenu__topic--hovered': hoveredTopicTitle === title,
})}
key={title}
role="button"
onClick={() => setSelectedTopicTitle(title)}
onClick={() => onClick(topic)}
onMouseEnter={() => handleMouseEnter(title)}
onMouseLeave={handleMouseLeave}
tabIndex={0}
Expand Down
Expand Up @@ -19,12 +19,18 @@
color: var(--color-sail-gray-500);
font-size: 14px;
font-weight: 400;
margin-bottom: 40px;
margin-bottom: 32px;
}

&__content-title {
color: var(--color-sail-blue-900);
font-weight: 700;
margin-bottom: 8px;
}

&__content-main {
color: var(--color-sail-gray-600);
// h3 regular
font-weight: 400;
}
}
Expand Up @@ -7,18 +7,19 @@ import './ProjectDetailsTopic.scss';
type Props = {
content: React.ReactNode;
iconType: IconType;
id: string;
title: string;
overview: string;
};

const ProjectDetailsTopic: FC<Props> = ({content, iconType, title, overview}) => {
const ProjectDetailsTopic: FC<Props> = ({content, iconType, id, title, overview}) => {
return (
<div className="ProjectDetailsTopic">
<div className="ProjectDetailsTopic" id={id}>
<Icon icon={iconType} size={96} />
<div className="ProjectDetailsTopic__content">
<h1 className="ProjectDetailsTopic__content-title">{title}</h1>
<h4 className="ProjectDetailsTopic__content-overview">{overview}</h4>
{content}
{typeof content === 'string' ? <h3 className="ProjectDetailsTopic__content-main">{content}</h3> : content}
</div>
</div>
);
Expand Down

0 comments on commit 2051dcf

Please sign in to comment.