aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Feed/Feed.tsx4
-rw-r--r--src/components/PollCard/PercentageBar.tsx12
-rw-r--r--src/components/PollCard/PollCard.tsx79
-rw-r--r--src/index.tsx10
-rw-r--r--src/pages/AuthPage/SignUpForm.tsx9
-rw-r--r--src/pages/FeedPage/FeedPage.tsx3
-rw-r--r--src/pages/ProfilePage/ProfileInfo.tsx4
-rw-r--r--src/pages/ProfilePage/ProfilePage.tsx3
-rw-r--r--src/requests.ts17
-rw-r--r--src/types.d.ts19
10 files changed, 95 insertions, 65 deletions
diff --git a/src/components/Feed/Feed.tsx b/src/components/Feed/Feed.tsx
index 3b8e16f..d81da99 100644
--- a/src/components/Feed/Feed.tsx
+++ b/src/components/Feed/Feed.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import { Poll } from '../../types';
+import { Poll } from 'which-types';
import PollCard from '../PollCard/PollCard';
interface PropTypes {
@@ -21,7 +21,7 @@ const Feed: React.FC<PropTypes> = ({ polls, navigate }) => {
return (
<div className={classes.root}>
- {polls.map(poll => <PollCard poll={poll} key={poll._id} navigate={navigate} />)}
+ {polls.map(poll => <PollCard initialPoll={poll} key={poll._id} navigate={navigate} />)}
</div>
);
};
diff --git a/src/components/PollCard/PercentageBar.tsx b/src/components/PollCard/PercentageBar.tsx
index 6a50a9e..a93d7b4 100644
--- a/src/components/PollCard/PercentageBar.tsx
+++ b/src/components/PollCard/PercentageBar.tsx
@@ -1,9 +1,11 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
+import LikeIcon from '@material-ui/icons/Favorite';
interface PropTypes {
value: number;
which: 'left' | 'right';
+ like: boolean;
}
const useStyles = makeStyles({
@@ -12,7 +14,9 @@ const useStyles = makeStyles({
color: 'white',
top: '86%',
fontSize: 20,
- textShadow: '0 0 3px black'
+ textShadow: '0 0 3px black',
+ display: 'flex',
+ alignItems: 'center'
},
left: {
left: 30
@@ -22,13 +26,13 @@ const useStyles = makeStyles({
}
});
-const PercentageBar: React.FC<PropTypes> = ({ value, which }) => {
+const PercentageBar: React.FC<PropTypes> = ({ value, which, like }) => {
const classes = useStyles();
return (
<div className={`${classes.root} ${classes[which]}`}>
- {value}
- %
+ {like && <LikeIcon />}
+ {`${value}%`}
</div>
);
};
diff --git a/src/components/PollCard/PollCard.tsx b/src/components/PollCard/PollCard.tsx
index baf896f..40f5fd7 100644
--- a/src/components/PollCard/PollCard.tsx
+++ b/src/components/PollCard/PollCard.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import {
Card,
@@ -7,43 +7,73 @@ import {
Avatar,
CardHeader
} from '@material-ui/core/';
-import { Poll } from '../../types';
+import { Which, Poll } from 'which-types';
+
import PercentageBar from './PercentageBar';
+import { post } from '../../requests';
interface PropTypes {
- poll: Poll;
+ initialPoll: Poll;
navigate: (prefix: string, id: string) => void;
}
const useStyles = makeStyles(theme => ({
root: {
maxWidth: theme.spacing(75),
- height: theme.spacing(63),
- margin: '20px auto'
+ height: 488,
+ margin: '40px auto'
},
images: {
height: theme.spacing(50),
- width: theme.spacing(38)
+ width: 300
},
imagesBlock: {
display: 'flex'
},
avatar: {
cursor: 'pointer'
+ },
+ rateLine: {
+ position: 'relative',
+ width: '100%',
+ height: theme.spacing(2),
+ backgroundColor: theme.palette.primary.light
+ },
+ fillRateLine: {
+ height: theme.spacing(2),
+ backgroundColor: theme.palette.primary.main,
+ transitionDuration: '0.5s'
}
}));
-
-const PollCard: React.FC<PropTypes> = ({ poll, navigate }) => {
+const PollCard: React.FC<PropTypes> = ({ initialPoll, navigate }) => {
+ const [poll, setPoll] = useState<Poll>(initialPoll);
const classes = useStyles();
- const { author, contents } = poll;
+ const { author, contents: { left, right }, userChoice } = poll;
const handleNavigate = () => {
navigate('profile', poll.author._id);
};
- const leftPercentage = Math.round(100 * (contents.left.votes / (contents.left.votes + contents.right.votes)));
- const rightPercentage = 100 - leftPercentage;
+ const vote = (which: Which) => {
+ if (userChoice) return;
+ post('votes/', { which, pollId: poll._id }).then(() => {
+ poll.contents[which].votes += 1;
+ poll.userChoice = which;
+ setPoll({ ...poll });
+ });
+ };
+
+ const handleLeft = () => vote('left');
+ const handleRight = () => vote('right');
+
+ const leftPercentage = Math.round(100 * (left.votes / (left.votes + right.votes)));
+
+ const percentage = {
+ left: leftPercentage,
+ right: 100 - leftPercentage
+ };
+ const dominant: Which = left.votes >= right.votes ? 'left' : 'right';
return (
<Card className={classes.root}>
@@ -52,33 +82,40 @@ const PollCard: React.FC<PropTypes> = ({ poll, navigate }) => {
<Avatar
aria-label="avatar"
src={author.avatarUrl}
- alt={author.name[0].toUpperCase()}
+ alt={author.username[0].toUpperCase()}
onClick={handleNavigate}
className={classes.avatar}
/>
)}
- title={author.name}
+ title={author.username}
/>
<div className={classes.imagesBlock}>
- <CardActionArea>
+ <CardActionArea onDoubleClick={handleLeft}>
<CardMedia
className={classes.images}
- image={contents.left.url}
+ image={left.url}
/>
- <PercentageBar value={leftPercentage} which="left" />
+ <PercentageBar value={percentage.left} which="left" like={userChoice === 'left'} />
</CardActionArea>
- <CardActionArea>
+ <CardActionArea onDoubleClick={handleRight}>
<CardMedia
className={classes.images}
- image={contents.right.url}
+ image={right.url}
/>
- <PercentageBar value={rightPercentage} which="right" />
+ <PercentageBar value={percentage.right} which="right" like={userChoice === 'right'} />
</CardActionArea>
</div>
+ <div className={classes.rateLine}>
+ <div
+ className={classes.fillRateLine}
+ style={{
+ width: `${percentage[dominant]}%`,
+ float: dominant
+ }}
+ />
+ </div>
</Card>
);
};
-
export default PollCard;
-
diff --git a/src/index.tsx b/src/index.tsx
index 1777e90..50b19f7 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -9,11 +9,12 @@ import { CssBaseline } from '@material-ui/core';
import teal from '@material-ui/core/colors/teal';
import 'typeface-roboto';
+import { User } from 'which-types';
import Header from './components/Header/Header';
import ProfilePage from './pages/ProfilePage/ProfilePage';
import FeedPage from './pages/FeedPage/FeedPage';
import AuthPage from './pages/AuthPage/AuthPage';
-import { User, Page } from './types';
+import { Page } from './types';
import { get, post } from './requests';
import ScrollTopArrow from './components/ScrollTopArrow/ScrollTopArrow';
@@ -21,7 +22,8 @@ import ScrollTopArrow from './components/ScrollTopArrow/ScrollTopArrow';
const theme = createMuiTheme({
palette: {
primary: {
- main: teal[700]
+ main: teal[700],
+ light: teal[100]
}
}
});
@@ -53,10 +55,10 @@ const App: React.FC = () => {
}
};
- const logIn = (name: string, password: string): Promise<boolean> => {
+ const logIn = (username: string, password: string): Promise<boolean> => {
return post('/authentication', {
strategy: 'local',
- name,
+ username,
password
}).then(response => {
const me = response.data.user;
diff --git a/src/pages/AuthPage/SignUpForm.tsx b/src/pages/AuthPage/SignUpForm.tsx
index 2769eb0..0e3d0c7 100644
--- a/src/pages/AuthPage/SignUpForm.tsx
+++ b/src/pages/AuthPage/SignUpForm.tsx
@@ -31,12 +31,11 @@ const SignUpForm: React.FC<PropTypes> = ({ logIn }) => {
const inputRefPassword = useRef<HTMLInputElement>();
const onClick = () => {
- const name = inputRef.current?.value;
+ const username = inputRef.current?.value;
const password = inputRefPassword.current?.value;
- const newUser = { name, password };
- if (name && password) {
- post('/users', newUser).then(() => {
- logIn(name, password);
+ if (username && password) {
+ post('/users', { username, password }).then(() => {
+ logIn(username, password);
});
}
};
diff --git a/src/pages/FeedPage/FeedPage.tsx b/src/pages/FeedPage/FeedPage.tsx
index fd75190..937b0a9 100644
--- a/src/pages/FeedPage/FeedPage.tsx
+++ b/src/pages/FeedPage/FeedPage.tsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
-import { Poll } from '../../types';
+import { Poll } from 'which-types';
+
import Feed from '../../components/Feed/Feed';
import { get } from '../../requests';
diff --git a/src/pages/ProfilePage/ProfileInfo.tsx b/src/pages/ProfilePage/ProfileInfo.tsx
index bddecd8..7208ec8 100644
--- a/src/pages/ProfilePage/ProfileInfo.tsx
+++ b/src/pages/ProfilePage/ProfileInfo.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { Avatar } from '@material-ui/core/';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button/Button';
-import { User } from '../../types';
+import { User } from 'which-types';
interface PropTypes {
user: User | undefined;
@@ -41,7 +41,7 @@ const ProfileInfo: React.FC<PropTypes> = ({ user, logOut }) => {
<div>
<Avatar className={classes.avatar} src={user?.avatarUrl} />
<div className={classes.name}>
- {user?.name}
+ {user?.username}
</div>
<div className={classes.profileMenu}>
<div style={{ borderBottom: '1px solid green', color: 'green' }} className={classes.menuButton}>
diff --git a/src/pages/ProfilePage/ProfilePage.tsx b/src/pages/ProfilePage/ProfilePage.tsx
index 0f5fb2b..363d4ff 100644
--- a/src/pages/ProfilePage/ProfilePage.tsx
+++ b/src/pages/ProfilePage/ProfilePage.tsx
@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
-import { User, Poll } from '../../types';
+import { User, Poll } from 'which-types';
+
import ProfileInfo from './ProfileInfo';
import Feed from '../../components/Feed/Feed';
import { get } from '../../requests';
diff --git a/src/requests.ts b/src/requests.ts
index 486502d..4cfd37b 100644
--- a/src/requests.ts
+++ b/src/requests.ts
@@ -1,10 +1,15 @@
-import axios, { AxiosResponse } from 'axios';
+import axios from 'axios';
+import _ from 'lodash';
-type Request = (url: string, data?: Record<string, unknown>) => Promise<AxiosResponse>;
+const requests = axios.create({
+ baseURL: 'http://localhost:3030'
+});
-const baseApiUrl = 'http://localhost:3030';
+requests.interceptors.request.use(config => {
+ const token = localStorage.getItem('token');
+ return _.set(config, 'headers.Authorization', token);
+});
-export const get: Request = url => axios.get(baseApiUrl + url);
-export const del: Request = url => axios.delete(baseApiUrl + url);
-export const post: Request = (url, data) => axios.post(baseApiUrl + url, data);
+export const { get, post, put } = requests;
+export default requests;
diff --git a/src/types.d.ts b/src/types.d.ts
index a62eec8..73346ce 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -2,23 +2,4 @@ export interface Page {
prefix: string;
id: string;
}
-export interface User {
- name: string;
- avatarUrl: string;
- _id: string;
-}
-
-interface ImageData {
- url: string;
- votes: number;
-}
-
-export interface Poll {
- _id: string;
- author: User;
- contents: {
- left: ImageData;
- right: ImageData;
- };
-}