diff options
Diffstat (limited to 'src/containers')
-rw-r--r-- | src/containers/Feed/PollSubmission.tsx | 102 | ||||
-rw-r--r-- | src/containers/Feed/PollSubmissionImage.tsx | 95 | ||||
-rw-r--r-- | src/containers/PollCreation/ImageInput.tsx | 10 | ||||
-rw-r--r-- | src/containers/PollCreation/PollCreation.tsx | 25 | ||||
-rw-r--r-- | src/containers/PollCreation/useS3Preupload.ts | 29 | ||||
-rw-r--r-- | src/containers/Profile/ProfileInfo.tsx | 77 |
6 files changed, 92 insertions, 246 deletions
diff --git a/src/containers/Feed/PollSubmission.tsx b/src/containers/Feed/PollSubmission.tsx deleted file mode 100644 index faaf2bd..0000000 --- a/src/containers/Feed/PollSubmission.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useState } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Collapse from '@material-ui/core/Collapse'; -import { - Button, - Card, - ClickAwayListener, - Divider -} from '@material-ui/core'; -import { Poll } from 'which-types'; -import { useSnackbar } from 'notistack'; -import axios from 'axios'; - -import PollSubmissionImage from './PollSubmissionImage'; -import UserStrip from '../../components/UserStrip/UserStrip'; -import { get, post } from '../../requests'; -import { useAuth } from '../../hooks/useAuth'; - -interface PropTypes{ - addPoll: (poll: Poll) => void; -} - -const useStyles = makeStyles(theme => ({ - root: { - marginBottom: theme.spacing(4) - }, - images: { - height: theme.spacing(50), - display: 'flex' - } -})); - -const PollSubmission: React.FC<PropTypes> = ({ addPoll }) => { - const classes = useStyles(); - const [expanded, setExpanded] = useState(false); - const [left, setLeft] = useState<File>(); - const [right, setRight] = useState<File>(); - const { enqueueSnackbar } = useSnackbar(); - const { user } = useAuth(); - - const readyToSubmit = left && right; - - const handleClickAway = () => { - setExpanded(false); - }; - - const uploadImage = (file?: File) => { - const headers = { 'Content-Type': 'image/png' }; - return get('/files') - .then(response => response.data) - .then(uploadUrl => axios.put(uploadUrl, file, { headers })) - .then(response => { - const { config: { url } } = response; - return url && url.slice(0, url.indexOf('.png') + 4); - }); - }; - - const handleClick = async () => { - if (expanded && readyToSubmit) { - const [leftUrl, rightUrl] = await Promise.all([uploadImage(left), uploadImage(right)]); - - const contents = { - left: { url: leftUrl }, - right: { url: rightUrl } - }; - - post('/polls/', { contents }).then(response => { - addPoll(response.data); - enqueueSnackbar('Your poll has been successfully created!', { - variant: 'success' - }); - }); - } - setExpanded(!expanded); - }; - - return ( - <ClickAwayListener onClickAway={handleClickAway}> - <Card className={classes.root}> - <Collapse in={expanded} timeout="auto" unmountOnExit> - {user && <UserStrip user={user} info="" />} - <Divider /> - <div className={classes.images}> - <PollSubmissionImage file={left} setFile={setLeft} /> - <PollSubmissionImage file={right} setFile={setRight} /> - </div> - </Collapse> - <Button - color="primary" - disabled={expanded && !readyToSubmit} - variant={expanded ? 'contained' : 'outlined'} - onClick={handleClick} - fullWidth - > - {expanded ? 'Submit' : 'Create a Poll'} - </Button> - </Card> - </ClickAwayListener> - ); -}; - -export default PollSubmission; diff --git a/src/containers/Feed/PollSubmissionImage.tsx b/src/containers/Feed/PollSubmissionImage.tsx deleted file mode 100644 index cd67847..0000000 --- a/src/containers/Feed/PollSubmissionImage.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useFilePicker, utils } from 'react-sage'; -import { makeStyles } from '@material-ui/core/styles'; -import CloudUploadIcon from '@material-ui/icons/CloudUpload'; -import { CardActionArea, CardMedia, Typography } from '@material-ui/core'; -import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined'; - -interface PropTypes { - file: File | undefined; - setFile: (file: File | undefined) => void; -} - -const useStyles = makeStyles({ - root: { - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - alignItems: 'center' - }, - clearIcon: { - opacity: '.5', - fontSize: 50 - }, - media: { - height: '100%', - width: '100%', - display: 'flex', - justifyContent: 'center', - alignItems: 'center' - }, - text: { - textAlign: 'center' - } -}); - - -const PollSubmissionImage: React.FC<PropTypes> = ({ file, setFile }) => { - const classes = useStyles(); - const { files, onClick, HiddenFileInput } = useFilePicker(); - const [url, setUrl] = useState<string>(); - const [isMediaHover, setIsMediaHover] = useState(false); - - const handleMouseEnter = (): void => { - setIsMediaHover(true); - }; - - const handleMouseLeave = (): void => { - setIsMediaHover(false); - }; - - useEffect(() => { - if (files?.length) { - const chosenFile = files[0]; - setFile(chosenFile); - utils.loadFile(chosenFile).then(result => setUrl(result)); - } - }, [files, setFile]); - - - const handleClick = () => { - if (file) { - setFile(undefined); - } else onClick(); - }; - - - const Upload = ( - <> - <CloudUploadIcon fontSize="large" color="primary" /> - <Typography variant="h5" className={classes.text}> Upload an image </Typography> - <HiddenFileInput accept=".jpg, .jpeg, .png, .gif" multiple={false} /> - </> - ); - - const Media = ( - <CardMedia - image={url} - className={classes.media} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} - > - {isMediaHover && <CancelOutlinedIcon className={classes.clearIcon} />} - </CardMedia> - ); - - return ( - <> - <CardActionArea onClick={handleClick} className={classes.root}> - {file ? (url && Media) : Upload} - </CardActionArea> - </> - ); -}; - -export default PollSubmissionImage; diff --git a/src/containers/PollCreation/ImageInput.tsx b/src/containers/PollCreation/ImageInput.tsx index 475d527..e807865 100644 --- a/src/containers/PollCreation/ImageInput.tsx +++ b/src/containers/PollCreation/ImageInput.tsx @@ -10,6 +10,7 @@ import { Check, CancelOutlined } from '@material-ui/icons'; import AttachLink from '../../components/AttachLink/AttachLink'; import FileUpload from '../../components/FileUpload/FileUpload'; import BackgroundImage from '../../components/Image/BackgroundImage'; +import getLocalFileUrl from '../../utils/getLocalFileUrl'; interface PropTypes { callback: (file?: File | string) => void; @@ -59,9 +60,12 @@ const ImageInput: React.FC<PropTypes> = ({ callback, progress }) => { callback(undefined); }; - const childrenCallback = (fileUrl: string, file?: File) => { - setUrl(fileUrl); - callback(file || fileUrl); + const childrenCallback = (value: File | string) => { + if (value instanceof File) { + getLocalFileUrl(value).then(localUrl => setUrl(localUrl)); + } else setUrl(value); + + callback(value); }; const Upload = ( diff --git a/src/containers/PollCreation/PollCreation.tsx b/src/containers/PollCreation/PollCreation.tsx index 03ab905..ecc6757 100644 --- a/src/containers/PollCreation/PollCreation.tsx +++ b/src/containers/PollCreation/PollCreation.tsx @@ -1,4 +1,3 @@ -/* eslint-disable */ import React from 'react'; import Bluebird from 'bluebird'; import { useHistory } from 'react-router-dom'; @@ -12,12 +11,12 @@ import { } from '@material-ui/core'; import { useSnackbar } from 'notistack'; +import useS3Preupload from './useS3Preupload'; import ImageInput from './ImageInput'; import UserStrip from '../../components/UserStrip/UserStrip'; import { post } from '../../requests'; import { useAuth } from '../../hooks/useAuth'; import { useFeed } from '../../hooks/APIClient'; -import useS3Preupload from '../../hooks/useS3Preupload'; const useStyles = makeStyles(theme => ({ @@ -38,23 +37,21 @@ const PollCreation: React.FC = () => { const { user } = useAuth(); const { mutate: updateFeed } = useFeed(); const { - setValue: setLeft, - progress: progressLeft, + file: left, + setFile: setLeft, resolve: resolveLeft, - isReady: isLeftReady + progress: leftProgress } = useS3Preupload(); const { - setValue: setRight, - progress: progressRight, + file: right, + setFile: setRight, resolve: resolveRight, - isReady: isRightReady + progress: rightProgress } = useS3Preupload(); const handleClick = async () => { try { const [leftUrl, rightUrl] = await Bluebird.all([resolveLeft(), resolveRight()]); - console.log('leftUrl', leftUrl); - console.log('rightUrl', rightUrl); const contents = { left: { url: leftUrl }, @@ -79,16 +76,16 @@ const PollCreation: React.FC = () => { {user && <UserStrip user={user} info="" />} <Divider /> <div className={classes.images}> - <ImageInput callback={setLeft} progress={progressLeft} /> - <ImageInput callback={setRight} progress={progressRight} /> + <ImageInput callback={setLeft} progress={leftProgress} /> + <ImageInput callback={setRight} progress={rightProgress} /> </div> { - progressLeft || progressRight + leftProgress || rightProgress ? <LinearProgress color="primary" /> : ( <Button color="primary" - disabled={!(isLeftReady && isRightReady)} + disabled={!(left && right)} variant="contained" onClick={handleClick} fullWidth diff --git a/src/containers/PollCreation/useS3Preupload.ts b/src/containers/PollCreation/useS3Preupload.ts new file mode 100644 index 0000000..0e2a8c4 --- /dev/null +++ b/src/containers/PollCreation/useS3Preupload.ts @@ -0,0 +1,29 @@ +import { useState, useCallback } from 'react'; +import uploadFileToS3 from '../../utils/uploadFileToS3'; + +interface Hook { + file: File | string | undefined; + setFile: (value: File | string | undefined) => void; + resolve: () => Promise<string>; + progress: number; +} + +export default (): Hook => { + const [file, setFile] = useState<File | string>(); + const [progress, setProgress] = useState<number>(0); + + const resolve = useCallback(async (quality?: number): Promise<string> => { + // Indicate start + setProgress(0.01); + + if (file instanceof File) return uploadFileToS3(file, quality, setProgress); + return file || ''; + }, [file]); + + return { + file, + setFile, + resolve, + progress + }; +}; diff --git a/src/containers/Profile/ProfileInfo.tsx b/src/containers/Profile/ProfileInfo.tsx index c9831f3..e2fb0a9 100644 --- a/src/containers/Profile/ProfileInfo.tsx +++ b/src/containers/Profile/ProfileInfo.tsx @@ -1,16 +1,16 @@ -import React from 'react'; -import { Badge, Typography } from '@material-ui/core/'; +import React, { useState, useCallback } from 'react'; +import { Badge, Typography, CircularProgress } from '@material-ui/core/'; +import { CameraAlt, CheckCircleOutline } from '@material-ui/icons/'; import { makeStyles } from '@material-ui/core/styles'; -import { User } from 'which-types'; -import CameraAltIcon from '@material-ui/icons/CameraAlt'; -import VerifiedIcon from '@material-ui/icons/CheckCircleOutline'; import Skeleton from '@material-ui/lab/Skeleton'; +import { User } from 'which-types'; + import Highlight from './Highlight'; -import AttachLink from '../../components/AttachLink/AttachLink'; +import FileUpload from '../../components/FileUpload/FileUpload'; import Avatar from '../../components/Avatar/Avatar'; import { patch } from '../../requests'; import { useAuth } from '../../hooks/useAuth'; - +import uploadFileToS3 from '../../utils/uploadFileToS3'; interface PropTypes { savedPolls: number; @@ -66,7 +66,9 @@ const useStyles = makeStyles(theme => ({ }, avatarContainer: { position: 'relative', - textAlign: 'center' + display: 'flex', + justifyContent: 'center', + alignItems: 'center' }, menuNumber: { fontWeight: 800, @@ -78,8 +80,11 @@ const useStyles = makeStyles(theme => ({ skeleton: { margin: '10px auto', borderRadius: 2 + }, + progress: { + position: 'absolute', + color: 'white' } - })); @@ -88,14 +93,18 @@ const ProfileInfo: React.FC<PropTypes> = ({ }) => { const classes = useStyles(); const { user } = useAuth(); + const [progress, setProgress] = useState<number>(0); + const dateSince = new Date(userInfo?.createdAt || '').toLocaleDateString(); - const patchAvatar = (url: string) => { - const id = user?._id; - patch(`/users/${id}`, { avatarUrl: url }).then(res => { - setUserInfo(res.data); - }); - }; + const handleUpdateAvatar = useCallback(async (file: File) => { + if (user) { + uploadFileToS3(file, 0.8, setProgress) + .then(avatarUrl => patch(`/users/${user._id}`, { avatarUrl })) + .then(response => setUserInfo(response.data)) + .then(() => setProgress(0)); + } + }, [user, setUserInfo]); return ( <div className={classes.root}> @@ -104,24 +113,28 @@ const ProfileInfo: React.FC<PropTypes> = ({ ? <Skeleton animation="wave" variant="circle" width={150} height={150} className={classes.avatar} /> : userInfo?._id === user?._id ? ( - <AttachLink callback={patchAvatar}> - <div className={classes.avatarContainer}> - <Badge - overlap="circle" - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'right' - }} - badgeContent={( + <div className={classes.avatarContainer}> + <Badge + overlap="circle" + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'right' + }} + className={classes.avatarContainer} + badgeContent={( + <FileUpload callback={handleUpdateAvatar}> <div className={classes.badge}> - <CameraAltIcon /> + <CameraAlt /> </div> - )} - > - <Avatar className={classes.avatar} user={userInfo} /> - </Badge> - </div> - </AttachLink> + </FileUpload> + )} + > + <Avatar className={classes.avatar} user={userInfo} /> + {progress > 0 && ( + <CircularProgress variant="static" value={progress} className={classes.progress} /> + )} + </Badge> + </div> ) : <Avatar className={classes.avatar} user={userInfo} /> } @@ -131,7 +144,7 @@ const ProfileInfo: React.FC<PropTypes> = ({ : ( <Typography variant="h5" className={classes.name}> {userInfo?.username} - {userInfo?.verified && <VerifiedIcon className={classes.verified} color="primary" />} + {userInfo?.verified && <CheckCircleOutline className={classes.verified} color="primary" />} </Typography> ) } |