aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json6
-rw-r--r--package.json2
-rw-r--r--src/components/PollCard/PollCard.tsx60
-rw-r--r--src/components/UserStrip/UserStrip.tsx65
-rw-r--r--src/index.tsx8
-rw-r--r--src/pages/AuthPage/AuthPage.tsx6
-rw-r--r--src/pages/AuthPage/SignInForm.tsx25
-rw-r--r--src/pages/AuthPage/SignUpForm.tsx33
-rw-r--r--src/pages/FeedPage/FeedPage.tsx2
-rw-r--r--src/requests.ts8
10 files changed, 149 insertions, 66 deletions
diff --git a/package-lock.json b/package-lock.json
index f9311e4..84e7815 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14118,9 +14118,9 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"which-types": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/which-types/-/which-types-1.4.1.tgz",
- "integrity": "sha512-ZtN3cDwz/fQbJBwrItsZ0jpGafReTd/fIffHNQtFW4THrZqi8z4qnFTbyu1M6LnAmPlwU/FaRLZPfd67ZQ4mFw=="
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/which-types/-/which-types-1.4.2.tgz",
+ "integrity": "sha512-nwcohvhH+VEA11cReLi/BgeuKHJYH7VM2BWe9OIX89CB+iaZ0+wb6oLFcIP6Vp6jw3k93yoPMe9pMBsOi4kj6w=="
},
"word-wrap": {
"version": "1.2.3",
diff --git a/package.json b/package.json
index 98f44df..ca1967a 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"react-icons": "^3.10.0",
"react-scripts": "3.4.1",
"typeface-roboto": "0.0.75",
- "which-types": "^1.4.1"
+ "which-types": "^1.4.2"
},
"scripts": {
"start": "react-scripts start",
diff --git a/src/components/PollCard/PollCard.tsx b/src/components/PollCard/PollCard.tsx
index 40f5fd7..d3b4fc2 100644
--- a/src/components/PollCard/PollCard.tsx
+++ b/src/components/PollCard/PollCard.tsx
@@ -3,13 +3,12 @@ import { makeStyles } from '@material-ui/core/styles';
import {
Card,
CardActionArea,
- CardMedia,
- Avatar,
- CardHeader
+ CardMedia
} from '@material-ui/core/';
import { Which, Poll } from 'which-types';
import PercentageBar from './PercentageBar';
+import UserStrip from '../UserStrip/UserStrip';
import { post } from '../../requests';
interface PropTypes {
@@ -17,6 +16,14 @@ interface PropTypes {
navigate: (prefix: string, id: string) => void;
}
+const DATE_FORMAT = {
+ month: 'long',
+ day: 'numeric',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+};
+
const useStyles = makeStyles(theme => ({
root: {
maxWidth: theme.spacing(75),
@@ -30,18 +37,19 @@ const useStyles = makeStyles(theme => ({
imagesBlock: {
display: 'flex'
},
- avatar: {
- cursor: 'pointer'
- },
rateLine: {
position: 'relative',
width: '100%',
height: theme.spacing(2),
- backgroundColor: theme.palette.primary.light
+ backgroundColor: theme.palette.primary.light,
+ transitionDuration: '0.5s'
+ },
+ highlight: {
+ backgroundColor: `${theme.palette.primary.main} !important`
},
fillRateLine: {
height: theme.spacing(2),
- backgroundColor: theme.palette.primary.main,
+ backgroundColor: theme.palette.primary.light,
transitionDuration: '0.5s'
}
}));
@@ -50,10 +58,7 @@ const PollCard: React.FC<PropTypes> = ({ initialPoll, navigate }) => {
const [poll, setPoll] = useState<Poll>(initialPoll);
const classes = useStyles();
const { author, contents: { left, right }, userChoice } = poll;
-
- const handleNavigate = () => {
- navigate('profile', poll.author._id);
- };
+ const date: string = new Date(poll.createdAt).toLocaleString('default', DATE_FORMAT);
const vote = (which: Which) => {
if (userChoice) return;
@@ -68,50 +73,33 @@ const PollCard: React.FC<PropTypes> = ({ initialPoll, navigate }) => {
const handleRight = () => vote('right');
const leftPercentage = Math.round(100 * (left.votes / (left.votes + right.votes)));
+ const rightPercentage = 100 - leftPercentage;
- const percentage = {
- left: leftPercentage,
- right: 100 - leftPercentage
- };
const dominant: Which = left.votes >= right.votes ? 'left' : 'right';
return (
<Card className={classes.root}>
- <CardHeader
- avatar={(
- <Avatar
- aria-label="avatar"
- src={author.avatarUrl}
- alt={author.username[0].toUpperCase()}
- onClick={handleNavigate}
- className={classes.avatar}
- />
- )}
- title={author.username}
- />
+ <UserStrip user={author} info={date} navigate={navigate} />
<div className={classes.imagesBlock}>
<CardActionArea onDoubleClick={handleLeft}>
<CardMedia
className={classes.images}
image={left.url}
/>
- <PercentageBar value={percentage.left} which="left" like={userChoice === 'left'} />
+ <PercentageBar value={leftPercentage} which="left" like={userChoice === 'left'} />
</CardActionArea>
<CardActionArea onDoubleClick={handleRight}>
<CardMedia
className={classes.images}
image={right.url}
/>
- <PercentageBar value={percentage.right} which="right" like={userChoice === 'right'} />
+ <PercentageBar value={rightPercentage} which="right" like={userChoice === 'right'} />
</CardActionArea>
</div>
- <div className={classes.rateLine}>
+ <div className={`${classes.rateLine} ${dominant === 'right' ? classes.highlight : ''}`}>
<div
- className={classes.fillRateLine}
- style={{
- width: `${percentage[dominant]}%`,
- float: dominant
- }}
+ className={`${classes.fillRateLine} ${dominant === 'left' ? classes.highlight : ''}`}
+ style={{ width: `${leftPercentage}%` }}
/>
</div>
</Card>
diff --git a/src/components/UserStrip/UserStrip.tsx b/src/components/UserStrip/UserStrip.tsx
new file mode 100644
index 0000000..6e84768
--- /dev/null
+++ b/src/components/UserStrip/UserStrip.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import VerifiedIcon from '@material-ui/icons/CheckCircleOutline';
+import {
+ Avatar,
+ CardHeader
+} from '@material-ui/core/';
+import { User } from 'which-types';
+
+
+interface PropTypes {
+ user: User;
+ info: string | JSX.Element
+ navigate: (prefix: string, id: string) => void;
+}
+
+
+const useStyles = makeStyles(theme => ({
+ root: {
+ display: 'flex',
+ alignItems: 'center'
+ },
+ verified: {
+ marginLeft: theme.spacing(0.5),
+ width: theme.spacing(2),
+ height: theme.spacing(2)
+ },
+ avatar: {
+ cursor: 'pointer'
+ }
+}));
+
+
+const UserStrip: React.FC<PropTypes> = ({ user, info, navigate }) => {
+ const classes = useStyles();
+ const {
+ username,
+ avatarUrl,
+ verified
+ } = user;
+
+ const handleNavigate = () => {
+ navigate('profile', user._id);
+ };
+
+ const avatar = (
+ <Avatar
+ src={avatarUrl}
+ alt={username[0].toUpperCase()}
+ onClick={handleNavigate}
+ className={classes.avatar}
+ />
+ );
+
+ const title = (
+ <div className={classes.root}>
+ {username}
+ {verified && <VerifiedIcon color="primary" className={classes.verified} />}
+ </div>
+ );
+
+ return <CardHeader avatar={avatar} title={title} subheader={info} />;
+};
+
+export default UserStrip;
diff --git a/src/index.tsx b/src/index.tsx
index 9139e4b..49c177b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -55,7 +55,7 @@ const App: React.FC = () => {
}
};
- const logIn = (username: string, password: string): Promise<boolean> => {
+ const logIn = (username: string, password: string, remember = true): Promise<boolean> => {
return post('/authentication', {
strategy: 'local',
username,
@@ -64,9 +64,10 @@ const App: React.FC = () => {
const me = response.data.user;
const token = response.data.accessToken;
setUser(me);
+ navigate('profile', me._id);
localStorage.setItem('userId', me._id);
localStorage.setItem('token', token);
- navigate('profile', me._id);
+ if (!remember) localStorage.setItem('shouldClear', 'true');
return true;
}).catch(() => false);
};
@@ -79,6 +80,9 @@ const App: React.FC = () => {
};
useEffect(() => {
+ if (localStorage.getItem('shouldClear')) {
+ localStorage.clear();
+ }
const userId = localStorage.getItem('userId');
if (userId) {
get(`/users/${userId}`).then(response => {
diff --git a/src/pages/AuthPage/AuthPage.tsx b/src/pages/AuthPage/AuthPage.tsx
index dc90c01..d2c2eec 100644
--- a/src/pages/AuthPage/AuthPage.tsx
+++ b/src/pages/AuthPage/AuthPage.tsx
@@ -5,7 +5,7 @@ import SignUpForm from './SignUpForm';
interface PropTypes {
- logIn: (name: string, password: string) => Promise<boolean>;
+ logIn: (name: string, password: string, remember?: boolean) => Promise<boolean>;
}
const useStyles = makeStyles({
@@ -29,8 +29,8 @@ const AuthPage: React.FC<PropTypes> = ({ logIn }) => {
};
const footerInfo = {
- signIn: ['Don\'t have an account?', 'Sign in'],
- signUp: ['Already have an account?', 'Sign up']
+ signIn: ['Don\'t have an account?', 'Sign up'],
+ signUp: ['Already have an account?', 'Sign in']
};
return (
diff --git a/src/pages/AuthPage/SignInForm.tsx b/src/pages/AuthPage/SignInForm.tsx
index c521abf..1dad153 100644
--- a/src/pages/AuthPage/SignInForm.tsx
+++ b/src/pages/AuthPage/SignInForm.tsx
@@ -1,10 +1,14 @@
import React, { useState, useRef } from 'react';
import { makeStyles } from '@material-ui/core/styles';
-import TextField from '@material-ui/core/TextField';
-import Button from '@material-ui/core/Button';
+import {
+ TextField,
+ Button,
+ FormControlLabel,
+ Switch
+} from '@material-ui/core';
interface PropTypes {
- logIn: (name: string, password: string) => Promise<boolean>;
+ logIn: (name: string, password: string, remember?: boolean) => Promise<boolean>;
}
const useStyles = makeStyles(theme => ({
@@ -26,15 +30,20 @@ const useStyles = makeStyles(theme => ({
const SignInForm: React.FC<PropTypes> = ({ logIn }) => {
const [error, setError] = useState<boolean>(false);
+ const [remember, setRemember] = useState<boolean>(true);
const classes = useStyles();
const nameRef = useRef<HTMLInputElement>();
const passwordRef = useRef<HTMLInputElement>();
- const onClick = async () => {
+ const handleCheck = () => {
+ setRemember(!remember);
+ };
+
+ const handleSubmit = async () => {
const name = nameRef.current?.value;
const password = passwordRef.current?.value;
if (name && password) {
- logIn(name, password).then(success => {
+ logIn(name, password, remember).then(success => {
if (!success) setError(true);
});
}
@@ -56,7 +65,11 @@ const SignInForm: React.FC<PropTypes> = ({ logIn }) => {
label="Password"
type="password"
/>
- <Button variant="contained" onClick={onClick}>submit</Button>
+ <FormControlLabel
+ control={<Switch color="primary" onClick={handleCheck} checked={remember} size="small" />}
+ label="Remember me"
+ />
+ <Button variant="contained" onClick={handleSubmit}>submit</Button>
</form>
</>
);
diff --git a/src/pages/AuthPage/SignUpForm.tsx b/src/pages/AuthPage/SignUpForm.tsx
index 0e3d0c7..25b79ff 100644
--- a/src/pages/AuthPage/SignUpForm.tsx
+++ b/src/pages/AuthPage/SignUpForm.tsx
@@ -1,4 +1,4 @@
-import React, { useRef } from 'react';
+import React, { useState, useRef } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
@@ -26,31 +26,42 @@ const useStyles = makeStyles(theme => ({
}));
const SignUpForm: React.FC<PropTypes> = ({ logIn }) => {
+ const [error, setError] = useState<boolean>(false);
const classes = useStyles();
- const inputRef = useRef<HTMLInputElement>();
- const inputRefPassword = useRef<HTMLInputElement>();
+ const usernameRef = useRef<HTMLInputElement>();
+ const emailRef = useRef<HTMLInputElement>();
+ const passwordRef = useRef<HTMLInputElement>();
const onClick = () => {
- const username = inputRef.current?.value;
- const password = inputRefPassword.current?.value;
+ const username = usernameRef.current?.value;
+ const password = passwordRef.current?.value;
+ const email = emailRef.current?.value;
if (username && password) {
- post('/users', { username, password }).then(() => {
+ post('/users', { username, password, email }).then(() => {
logIn(username, password);
});
- }
+ } else setError(true);
};
return (
<>
<div className={classes.formHeader}>Sign Up</div>
<form className={classes.root} noValidate autoComplete="off">
- <TextField inputRef={inputRef} id="standard-basic" label="Name" />
- <TextField id="standard-basic" label="Email" />
<TextField
- inputRef={inputRefPassword}
- id="standard-password-input"
+ inputRef={usernameRef}
+ label="Username"
+ error={error}
+ helperText={error && 'This field is required!'}
+ required
+ />
+ <TextField inputRef={emailRef} label="Email" />
+ <TextField
+ inputRef={passwordRef}
label="Password"
type="password"
+ required
+ error={error}
+ helperText={error && 'This field is required!'}
/>
<Button variant="contained" onClick={onClick}>submit</Button>
</form>
diff --git a/src/pages/FeedPage/FeedPage.tsx b/src/pages/FeedPage/FeedPage.tsx
index 937b0a9..b7d719e 100644
--- a/src/pages/FeedPage/FeedPage.tsx
+++ b/src/pages/FeedPage/FeedPage.tsx
@@ -12,7 +12,7 @@ const FeedPage: React.FC<PropTypes> = ({ navigate }) => {
const [polls, setPolls] = useState<Poll[]>([]);
useEffect(() => {
- get('/polls').then(response => {
+ get('/feed').then(response => {
setPolls(response.data);
});
}, []);
diff --git a/src/requests.ts b/src/requests.ts
index e18a056..b02329d 100644
--- a/src/requests.ts
+++ b/src/requests.ts
@@ -1,9 +1,11 @@
import axios from 'axios';
import _ from 'lodash';
-const requests = axios.create({
- baseURL: 'http://localhost:3030'
-});
+const baseURL = process.env.NODE_ENV === 'production'
+ ? 'https://which-api.herokuapp.com'
+ : 'http://localhost:3030';
+
+const requests = axios.create({ baseURL });
requests.interceptors.request.use(config => {
const token = localStorage.getItem('token');