Skip to content

Commit

Permalink
Add One on One page
Browse files Browse the repository at this point in the history
  • Loading branch information
recrsn committed Feb 13, 2021
1 parent 678d8f3 commit 1eebd6e
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 7 deletions.
4 changes: 2 additions & 2 deletions frontend/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export default function App() {
<AuthProvider>
<BrowserRouter>
<Switch>
<Route path="/login">
<Route exact={true} path="/login">
<LoginPage/>
</Route>
<ProtectedRoute exact={true} path="/">
<ProtectedRoute path="/">
<Snowflake/>
</ProtectedRoute>
</Switch>
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/components/Snowflake.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import React from "react";
import Navbar from "./header/Navbar";
import {Route, Switch} from "react-router-dom";
import {Redirect, Route, Switch} from "react-router-dom";
import FeedPage from "./feed/FeedPage";
import styles from './Snowflake.module.css';
import OneOnOnesPage from "./one-on-ones/OneOnOnesPage";

export default function Snowflake() {
return (
<>
<Navbar/>
<div className={styles.container}>
<Switch>
<Route path="/" component={FeedPage}/>
<Route path="/" exact={true}>
<Redirect to="/feed"/>
</Route>
<Route path="/feed" component={FeedPage}/>
<Route path="/1-on-1s" component={OneOnOnesPage}/>
</Switch>
</div>
</>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/header/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export default function Navbar() {
</div>

<div className="navbar-end">
<NavbarMenuItem to="/" icon="home-outline" label="Feed"/>
<NavbarMenuItem to="/" icon="people-outline" label="1-on-1s"/>
<NavbarMenuItem to="/feed" icon="home-outline" label="Feed"/>
<NavbarMenuItem to="/1-on-1s" icon="people-outline" label="1-on-1s"/>
<NavbarMenuItem to="/" icon="rocket-outline" label="Personal objectives"/>
<Notifications/>
<ProfileDropdownMenu/>
Expand Down
89 changes: 89 additions & 0 deletions frontend/src/components/one-on-ones/OneOnOneDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {Link, useParams} from "react-router-dom";
import React, {useCallback} from "react";
import {oneOnOneById} from "../../lib/api";
import {useAsync} from "../../hooks/use-async";
import {useAuth} from "../../hooks/use-auth";
import Loading from "../Loading";
import {isCreatedByUser} from "../../lib/utils";
import ScheduleMeetingButton from "./ScheduleMeetingButton";

type OneOnOneDetailRouterParams = {
id: string
}

export default function OneOnOneDetail() {
const {id} = useParams<OneOnOneDetailRouterParams>();
const loadOneOnOneById = useCallback(() => oneOnOneById(id), [id]);
const {value: oneOnOne, status} = useAsync(loadOneOnOneById);
const {user} = useAuth();

return (
<div className="container p-6">
{status === "pending" && <Loading/>}
{oneOnOne && (
<article className="appreciation media">
<figure className="media-left is-flex">
<div className="participants-avatar image is-48x48">
<img alt={oneOnOne.createdBy.name} className="is-rounded"
src={oneOnOne.createdBy.profilePic}/>
</div>
<div className="participants-avatar image is-48x48">
<img alt={oneOnOne.user.name} className="is-rounded"
src={oneOnOne.user.profilePic}/>
</div>
</figure>
<div className="media-content">
<div className="content">
<div className="block is-flex">
<h1 className="title is-flex-grow-1">
{isCreatedByUser(oneOnOne, user) ?
`Your 1:1 with ${oneOnOne.user.name}` :
`${oneOnOne.createdBy.name}}'s 1:1 with you`}
</h1>
<ScheduleMeetingButton
title={`${oneOnOne.createdBy.name} 1:1 with ${oneOnOne.user.name}`}/>
</div>
<div className="block">
<h3>Action items</h3>
<ul className="action-items">
{oneOnOne.actionItems.map(actionItem => (
<li key={actionItem.id} className="block is-flex align-items-center">
<button className="clear-button mr-1 is-clickable">
{actionItem.state ? (
<span className="icon has-text-success">
<ion-icon size="large" name="checkmark-circle"/>
</span>
) : (
<span className="icon">
<ion-icon size="large" name="checkmark-circle-outline"/>
</span>
)}
</button>
<span className="image is-24x24 mx-1">
<img alt={`Created by ${actionItem.createdBy.name}`} className="is-rounded"
src={actionItem.createdBy.profilePic}/>
</span>
<Link className="mr-1" to={`/profile/${actionItem.createdBy.username}`}>
{actionItem.createdBy.name}
</Link>
<span>
{actionItem.content}
{/*TODO: add_mentions */}
</span>
</li>
))}
</ul>
<form className="block field">
<div className="control has-icons-left">
<input className="input" type="text" placeholder="Add action item..."/>
<span className="icon is-left">
<ion-icon name="add-circle-outline"/>
</span>
</div>
</form>
</div>
</div>
</div>
</article>)}
</div>);
}
54 changes: 54 additions & 0 deletions frontend/src/components/one-on-ones/OneOnOnesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {useAsync} from "../../hooks/use-async";
import {oneOnOnes} from "../../lib/api";
import {useAuth} from "../../hooks/use-auth";
import Loading from "../Loading";
import {isCreatedByUser} from "../../lib/utils";
import React from "react";
import { Link } from "react-router-dom";

export default function OneOnOneList() {

const {value: oneOnOneList, status} = useAsync(oneOnOnes);
const {user} = useAuth();

return (
<div className="menu">
<p className="menu-label is-flex">
<span className="is-flex-grow-1 is-size-6">People</span>
<button title="Schedule new" id="launch-one-on-one-form" className="clear-button">
<span className="icon is-medium">
<ion-icon class="is-size-4" name="add-circle-outline"/>
</span>
</button>
</p>
{status === "pending" && <Loading/>}
<ul className="menu-list">
{oneOnOneList?.map(oneOnOne => (
<li key={oneOnOne.id} >
<Link className="is-flex" to={`/1-on-1s/${(oneOnOne.id)}`}>
<figure className="media-left is-flex">
<div className="participants-avatar image is-48x48">
<img alt="Avatar of {{ o.created_by.name }}"
title={oneOnOne.createdBy.name}
className="is-rounded" src={oneOnOne.createdBy.profilePic}/>
</div>
<div className="participants-avatar image is-48x48">
<img alt="Avatar of {{ o.user.name }}"
title={oneOnOne.user.name}
className="is-rounded" src={oneOnOne.user.profilePic}/>
</div>
</figure>
<div className="content">
<h3 className="is-size-5 has-font-weight-bold">
{isCreatedByUser(oneOnOne, user) ? oneOnOne.user.name : oneOnOne.createdBy.name}
</h3>
<p className="help">
{isCreatedByUser(oneOnOne, user) ? oneOnOne.user.designation : oneOnOne.createdBy.designation}
</p>
</div>
</Link>
</li>
))}
</ul>
</div>);
}
23 changes: 23 additions & 0 deletions frontend/src/components/one-on-ones/OneOnOnesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import OneOnOneList from "./OneOnOnesList";
import OneOnOneDetail from "./OneOnOneDetail";
import {Route, Switch} from "react-router-dom";


export default function OneOnOnesPage() {
return (
<div className="container">
<div className="columns">
<div className="column is-one-fifth">
<OneOnOneList/>
</div>
<div className="column is-four-fifths">
<Switch>
<Route path="/1-on-1s/:id">
<OneOnOneDetail/>
</Route>
</Switch>
</div>
</div>
</div>)
}
12 changes: 12 additions & 0 deletions frontend/src/components/one-on-ones/ScheduleMeetingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";

export default function ScheduleMeetingButton({title}: { title: string }) {
return (
<a className="button is-medium is-primary"
href={`https://www.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(title)}`}>
<span className="icon is-medium">
<ion-icon size="large" name="calendar-outline"/>
</span>
<span>Schedule</span>
</a>)
}
26 changes: 26 additions & 0 deletions frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ export type Appreciation = {
viewerLike: Like
}

export type OneOnOne = {
id: number,
user: User,
createdAt: string,
createdBy: User
}

export type OneOnOneActionItem = {
id: number,
state: boolean
content: string
createdBy: User
}

export type OneOnOneDetail = OneOnOne & {
actionItems: OneOnOneActionItem[]
}

async function get<T>(url: string): Promise<T> {
const response = await axios.get(url, {
headers: authorizationHeaders()
Expand All @@ -48,3 +66,11 @@ export async function appreciationComments(id: number): Promise<Comment[]> {
export async function appreciationLikes(id: number): Promise<Like[]> {
return get(`/api/appreciations/${id}/likes`);
}

export async function oneOnOnes(): Promise<OneOnOne[]> {
return get("/api/one_on_ones")
}

export async function oneOnOneById(id: number | string): Promise<OneOnOneDetail> {
return get(`/api/one_on_ones/${id}`)
}
6 changes: 5 additions & 1 deletion frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {token} from "./auth";
import {token, User} from "./auth";

export function authorizationHeaders() {
return {
'Authorization': `Bearer ${token()}`
}
}

export function isCreatedByUser(object: { createdBy?: User }, user?: User) {
return user?.username === object.createdBy?.username;
}

0 comments on commit 1eebd6e

Please sign in to comment.