Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: node ../server/built/server.mjs
25,819 changes: 25,728 additions & 91 deletions client/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"typescript": "^3.7.5"
},
"scripts": {
"start": "react-scripts start",
"start": "react-scripts --openssl-legacy-provider start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
Expand Down
39 changes: 4 additions & 35 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,14 @@ class App extends Component<Props, State> {
constructor(props: Props) {
super(props);

this.state = {
isGoogleAPILoaded: false,
this.state = {
isGoogleAPILoaded: false,
};

// @ts-ignore -- .gapi does exist
this.googleAPI = window.gapi;
}

public async componentDidMount() {
const id = '6015aa416131eb0a74e89e52'
// get the first user... return that for now
try {
const userResponse = await fetch(`/users/${id}`);
if (userResponse.status !== 200) {
throw new Error(userResponse.statusText);
}
const user = await userResponse.json();
this.setState({ user });
} catch(err) {
console.error('Unable to find user');
}
Comment on lines -35 to -47
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed for now.


// this.googleAPI.load("auth2", async () => {
// await this.googleAPI.auth2.init({
// client_id: config.googleClientId,
// cookiepolicy: "single-host-origin",
// });

// this.setState({
// isGoogleAPILoaded: true,
// });
// });
}

public render() {
return (
<UserContext.Provider value={this.state}>
Expand Down Expand Up @@ -114,17 +88,12 @@ class App extends Component<Props, State> {
return this.state.user ? (
<Redirect
to={{
pathname: "/messages",
pathname: "/find-users",
state: { from: location },
}}
/>
) : (
<Index
onUserAuthenticated={this.onUserSelected}
googleAPI={
this.state.isGoogleAPILoaded ? this.googleAPI : undefined
}
/>
<Index onUserSelected={this.onUserSelected.bind(this)} />
);
}}
></Route>
Expand Down
10 changes: 7 additions & 3 deletions client/src/components/Avatar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ const Background = styled.div<{ color: string; size?: string }>`
? "100px"
: props.size === "medium"
? "75px"
: "50px"};
: props.size === "small"
? "50px"
: "30px"};
height: ${(props) =>
props.size === "large"
? "100px"
: props.size === "medium"
? "75px"
: "50px"};
: props.size === "small"
? "50px"
: "30px"};
position: relative;
overflow: hidden;
background-color: ${(props) => props.color};
Expand All @@ -38,7 +42,7 @@ const Shape = styled.div<{

interface Props {
thumbnail: string;
size?: "small" | "medium" | "large";
size?: "extra-small" | "small" | "medium" | "large";
}

class Avatar extends Component<Props> {
Expand Down
115 changes: 115 additions & 0 deletions client/src/components/Dropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, {
FC,
useState,
useEffect,
useRef,
MouseEvent,
ReactElement,
} from "react";
import styled from "styled-components";
import variables from "../../styles/variables"

const AnchorContainer = styled.div`
position: relative;
`;

const Anchor = styled.button`
position: relative;
border: none;
background-color: transparent;
padding: 2px;
cursor: pointer;

&:hover {
background-color: ${variables.color.offWhite};
}
`;
const Popover = styled.ul`
position: absolute;
bottom: 0;
right: 0;
z-index: 1;
transform: translateY(100%);
min-width: 100px;
background-color: ${variables.color.white};
box-shadow: 1px 1px 5px 1px rgba(30,30,30,0.3);
list-style-type: none;
padding: 0;
margin: 0;
`;

const PopoverItem = styled.li`
white-space: nowrap;

& + & {
border-top: 1px solid ${variables.color.offWhite};
}
`;

const PopoverItemButton = styled.button`
appearance: none;
background: none;
border: none;
text-align: right;
width: 100%;
padding: 10px;
cursor: pointer;

&:hover {
background-color: ${variables.color.offWhite};
}
`;

interface DropdownItem {
id: string;
label: string;
onClick: () => void;
}

interface Props {
target: ReactElement;
items: DropdownItem[];
}

const Dropdown: FC<Props> = (props) => {
const [isShowing, setIsShowing] = useState(false);
const anchorRef = useRef<HTMLButtonElement | undefined>(undefined);

useEffect(() => {
const closeDropdown = (event: Event) => {
if (!anchorRef.current?.contains(event.target as HTMLElement)) {
setIsShowing(false);
}
};

document.addEventListener("click", closeDropdown);

return () => {
document.removeEventListener("click", closeDropdown);
};
});

return (
<AnchorContainer>
<Anchor
// @ts-ignore
ref={anchorRef}
onClick={(event: MouseEvent) => {
console.log("event handler");
event.stopPropagation();
setIsShowing(!isShowing);
}}
>
{props.target}

</Anchor>
{isShowing && <Popover>{props.items.map(item => (
<PopoverItem key={item.id}>
<PopoverItemButton onClick={item.onClick}>{item.label}</PopoverItemButton>
</PopoverItem>
))}</Popover>}
</AnchorContainer>
);
};

export default Dropdown;
95 changes: 57 additions & 38 deletions client/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import React, { FC } from "react";
import { Link, useHistory } from "react-router-dom";
import { Container, Row, Col } from "react-grid-system";
import styled from "styled-components";
import variables from "../../styles/variables";
import { signOutUser } from "../../utils/userHelper";
import { injectUserContext, UserContextProps } from "../../context/UserContext";
import ContentContainer from "../ContentContainer";
import Avatar from "../Avatar";
import Dropdown from "../Dropdown";

interface HeaderLink {
path: string;
Expand Down Expand Up @@ -56,42 +57,60 @@ const EndAligner = styled.div`
grid-column-gap: 60px;
`;

class Header extends Component<Props> {
public render() {
return (
<HeaderContainer>
<ContentContainer>
<Container>
<Row justify="end">
<Col sm={12}>
<EndAligner>
<>
{this.props.links.map((link: HeaderLink) => {
return (
<LinkWrapper
key={link.path}
isSelected={link.path === window.location.pathname}
>
<StyledLink to={link.path}>{link.label}</StyledLink>
</LinkWrapper>
);
})}
{this.props.user && (
<LinkWrapper isSelected={false}>
<StyledButton onClick={signOutUser}>
Sign Out
</StyledButton>
const Header: FC<Props> = (props) => {
const history = useHistory();

return (
<HeaderContainer>
<ContentContainer>
<Container>
<Row justify="end">
<Col sm={12}>
<EndAligner>
<>
{props.links.map((link: HeaderLink) => {
return (
<LinkWrapper
key={link.path}
isSelected={link.path === window.location.pathname}
>
<StyledLink to={link.path}>{link.label}</StyledLink>
</LinkWrapper>
)}
</>
</EndAligner>
</Col>
</Row>
</Container>
</ContentContainer>
</HeaderContainer>
);
}
);
})}
{props.user && (
<Dropdown
target={
<Avatar
size="extra-small"
thumbnail={props.user.thumbnail}
/>
}
items={[
{
id: 'about-user',
label: `${props.user.name.first} ${props.user.name.last}`,
onClick: () => {
history.push(`/profiles/${props.user?._id || ""}`)
}
},
{
id: 'sign-out',
label: 'Sign Out',
onClick: () => {
window.location.href = window.location.origin;
}
}]}
/>
)}
</>
</EndAligner>
</Col>
</Row>
</Container>
</ContentContainer>
</HeaderContainer>
);
}

export default injectUserContext(Header);
60 changes: 60 additions & 0 deletions client/src/components/UsersList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import styled from "styled-components";
import { User } from "../../types";
import Button from "../Button";
import Avatar from "../Avatar";


const ListItem = styled.li`
display: flex;
align-items: center;
margin-bottom: 64px;
`;

const Details = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
margin-left: 16px;
`;

const ItemTitle = styled.h3`
margin-top: 0;
`;


interface Props {
users: User[];
onItemClick: (user: User) => void;
itemButtonLabel: string;
}

class UsersList extends React.Component<Props> {
constructor(props: Props) {
super(props);
}

public render() {
return (
<ul>
{this.props.users.map((user) => {
return (
<ListItem key={user._id}>
<Avatar thumbnail={user.thumbnail} size="medium" />
<Details>
<ItemTitle>
{user.name.first} {user.name.last}
</ItemTitle>
<Button onClick={this.props.onItemClick.bind(this, user)}>
{this.props.itemButtonLabel}
</Button>
</Details>
</ListItem>
);
})}
</ul>
);
}
}

export default UsersList;
Loading