diff options
author | Eugene Sokolov <eug-vs@keemail.me> | 2020-08-13 21:44:12 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-13 21:44:12 +0300 |
commit | 6ace75beae6ab6a466c4d0a9a60ca30aaad0a87c (patch) | |
tree | 7c1611c8dd7d45a72bb2316f78ea3980b27e09a5 | |
parent | d1e0dcd8538a61184eca50fbf7769c6d2943ff6b (diff) | |
parent | 474dd922ac0512f1e0f64c145e9f76d2b10a1ba5 (diff) | |
download | which-ui-6ace75beae6ab6a466c4d0a9a60ca30aaad0a87c.tar.gz |
Merge pull request #79 from which-ecosystem/improved-poll-creation
PollCreation redesign
-rw-r--r-- | src/components/AttachLink/AttachLink.tsx | 42 | ||||
-rw-r--r-- | src/components/AttachLink/Modal.tsx (renamed from src/components/UploadImage/UploadImage.tsx) | 8 | ||||
-rw-r--r-- | src/components/FileUpload/FileUpload.tsx | 47 | ||||
-rw-r--r-- | src/containers/Page/Page.tsx | 2 | ||||
-rw-r--r-- | src/containers/PollCreation/PollCreation.tsx | 36 | ||||
-rw-r--r-- | src/containers/PollCreation/PollCreationImage.tsx | 85 | ||||
-rw-r--r-- | src/containers/Profile/ProfileInfo.tsx | 15 | ||||
-rw-r--r-- | src/index.tsx | 2 |
8 files changed, 150 insertions, 87 deletions
diff --git a/src/components/AttachLink/AttachLink.tsx b/src/components/AttachLink/AttachLink.tsx new file mode 100644 index 0000000..e73f5c1 --- /dev/null +++ b/src/components/AttachLink/AttachLink.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; +import Button from '@material-ui/core/Button'; +import LinkIcon from '@material-ui/icons/Link'; +import Modal from './Modal'; + +interface PropTypes { + callback: (url: string) => void; +} + +const AttachLink: React.FC<PropTypes> = ({ callback, children }) => { + const [isOpen, setIsOpen] = useState<boolean>(false); + + const handleOpen = (): void => { + setIsOpen(true); + }; + + const defaultButton = ( + <Button + onClick={handleOpen} + variant="outlined" + color="primary" + startIcon={<LinkIcon />} + > + Attach a link + </Button> + ); + + const child = children && React.Children.toArray(children)[0]; + + return ( + <> + <Modal callback={callback} isOpen={isOpen} setIsOpen={setIsOpen} /> + { + React.isValidElement(child) + ? React.cloneElement(child, { onClick: handleOpen }) + : defaultButton + } + </> + ); +}; + +export default AttachLink; diff --git a/src/components/UploadImage/UploadImage.tsx b/src/components/AttachLink/Modal.tsx index 238d5cd..fa58fc4 100644 --- a/src/components/UploadImage/UploadImage.tsx +++ b/src/components/AttachLink/Modal.tsx @@ -15,7 +15,7 @@ interface PropTypes { callback: (url: string) => void; } -const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { +const Modal: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { const [url, setUrl] = useState<string>(''); const handleClose = () => { @@ -44,10 +44,10 @@ const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { return ( <div> <Dialog open={isOpen} onClose={handleClose}> - <DialogTitle>Upload an Image</DialogTitle> + <DialogTitle>Upload via link</DialogTitle> <DialogContent> <DialogContentText> - Unfortunetly we do not support uploading images yet. Please provide a valid URL to your image: + Provide a valid URL to your image: </DialogContentText> <TextField autoFocus @@ -73,4 +73,4 @@ const UploadImage: React.FC<PropTypes> = ({ setIsOpen, isOpen, callback }) => { ); }; -export default UploadImage; +export default Modal; diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx new file mode 100644 index 0000000..67d280d --- /dev/null +++ b/src/components/FileUpload/FileUpload.tsx @@ -0,0 +1,47 @@ +import React, { useEffect } from 'react'; +import { useFilePicker, utils } from 'react-sage'; +import Button from '@material-ui/core/Button'; +import CloudUpload from '@material-ui/icons/CloudUpload'; + +interface PropTypes { + callback: (fileUrl: string, file: File) => void; +} + + +const FileUpload: React.FC<PropTypes> = ({ callback, children }) => { + const { files, onClick, HiddenFileInput } = useFilePicker(); + + useEffect(() => { + if (files?.length) { + const file = files[0]; + utils.loadFile(file).then(url => callback(url, file)); + } + }, [files, callback]); + + const child = children && React.Children.toArray(children)[0]; + + const defaultButton = ( + <Button + onClick={onClick} + variant="contained" + color="primary" + size="large" + startIcon={<CloudUpload />} + > + Upload + </Button> + ); + + return ( + <> + <HiddenFileInput accept=".jpg, .jpeg, .png, .gif" multiple={false} /> + { + React.isValidElement(child) + ? React.cloneElement(child, { onClick }) + : defaultButton + } + </> + ); +}; + +export default FileUpload; diff --git a/src/containers/Page/Page.tsx b/src/containers/Page/Page.tsx index 19cf6aa..848ca1d 100644 --- a/src/containers/Page/Page.tsx +++ b/src/containers/Page/Page.tsx @@ -36,7 +36,7 @@ const Page: React.FC = () => { preventDuplicate maxSnack={isMobile ? 1 : 3} anchorOrigin={{ - vertical: isMobile ? 'top' : 'bottom', + vertical: 'top', horizontal: 'right' }} > diff --git a/src/containers/PollCreation/PollCreation.tsx b/src/containers/PollCreation/PollCreation.tsx index 7501d3a..64ab7fd 100644 --- a/src/containers/PollCreation/PollCreation.tsx +++ b/src/containers/PollCreation/PollCreation.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; import { makeStyles } from '@material-ui/core/styles'; import { Button, @@ -6,7 +7,6 @@ import { Divider, Container } from '@material-ui/core'; -import { Poll } from 'which-types'; import { useSnackbar } from 'notistack'; import axios from 'axios'; @@ -14,10 +14,7 @@ import PollCreationImage from './PollCreationImage'; import UserStrip from '../../components/UserStrip/UserStrip'; import { get, post } from '../../requests'; import { useAuth } from '../../hooks/useAuth'; - -interface PropTypes{ - addPoll: (poll: Poll) => void; -} +import { useFeed } from '../../hooks/APIClient'; const useStyles = makeStyles(theme => ({ root: { @@ -29,41 +26,50 @@ const useStyles = makeStyles(theme => ({ } })); -const PollCreation: React.FC<PropTypes> = ({ addPoll }) => { +const PollCreation: React.FC = () => { const classes = useStyles(); - const [left, setLeft] = useState<File>(); - const [right, setRight] = useState<File>(); + const history = useHistory(); + const [left, setLeft] = useState<File | string>(); + const [right, setRight] = useState<File | string>(); const { enqueueSnackbar } = useSnackbar(); const { user } = useAuth(); + const { mutate: updateFeed } = useFeed(); const readyToSubmit = left && right; - const uploadImage = (file?: File) => { + const uploadFile = (file: File): Promise<string> => { 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); + return url?.slice(0, url?.indexOf('?')) || ''; }); }; + const resolveFile = async (file?: File | string): Promise<string> => { + if (file instanceof File) return uploadFile(file); + return file || ''; + }; + const handleClick = async () => { if (readyToSubmit) { - const [leftUrl, rightUrl] = await Promise.all([uploadImage(left), uploadImage(right)]); + const [leftUrl, rightUrl] = await Promise.all([resolveFile(left), resolveFile(right)]); const contents = { left: { url: leftUrl }, right: { url: rightUrl } }; - post('/polls/', { contents }).then(response => { - addPoll(response.data); + post('/polls/', { contents }).then(() => { + updateFeed(); enqueueSnackbar('Your poll has been successfully created!', { variant: 'success' }); }); + + history.push('/feed'); } }; @@ -73,8 +79,8 @@ const PollCreation: React.FC<PropTypes> = ({ addPoll }) => { {user && <UserStrip user={user} info="" />} <Divider /> <div className={classes.images}> - <PollCreationImage file={left} setFile={setLeft} /> - <PollCreationImage file={right} setFile={setRight} /> + <PollCreationImage callback={setLeft} /> + <PollCreationImage callback={setRight} /> </div> <Button color="primary" diff --git a/src/containers/PollCreation/PollCreationImage.tsx b/src/containers/PollCreation/PollCreationImage.tsx index d3203a6..1200b11 100644 --- a/src/containers/PollCreation/PollCreationImage.tsx +++ b/src/containers/PollCreation/PollCreationImage.tsx @@ -1,13 +1,17 @@ -import React, { useState, useEffect } from 'react'; -import { useFilePicker, utils } from 'react-sage'; +import React, { useState } from 'react'; 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'; +import { + CardActionArea, + CardMedia, + Typography +} from '@material-ui/core'; +import ClearIcon from '@material-ui/icons/CancelOutlined'; + +import AttachLink from '../../components/AttachLink/AttachLink'; +import FileUpload from '../../components/FileUpload/FileUpload'; interface PropTypes { - file: File | undefined; - setFile: (file: File | undefined) => void; + callback: (file?: File | string) => void; } const useStyles = makeStyles({ @@ -15,7 +19,8 @@ const useStyles = makeStyles({ display: 'flex', justifyContent: 'center', flexDirection: 'column', - alignItems: 'center' + alignItems: 'center', + width: '50%' }, clearIcon: { opacity: '.5', @@ -27,69 +32,41 @@ const useStyles = makeStyles({ display: 'flex', justifyContent: 'center', alignItems: 'center' - }, - text: { - textAlign: 'center' } }); -const PollCreationImage: React.FC<PropTypes> = ({ file, setFile }) => { +const PollCreationImage: React.FC<PropTypes> = ({ callback }) => { const classes = useStyles(); - const { files, onClick, HiddenFileInput } = useFilePicker(); const [url, setUrl] = useState<string>(); - const [isMediaHover, setIsMediaHover] = useState(false); - const handleMouseEnter = (): void => { - setIsMediaHover(true); + const handleClear = (): void => { + setUrl(undefined); + callback(undefined); }; - const handleMouseLeave = (): void => { - setIsMediaHover(false); + const childrenCallback = (fileUrl: string, file?: File) => { + setUrl(fileUrl); + callback(file || fileUrl); }; - 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} /> - </> + <div className={classes.root}> + <FileUpload callback={childrenCallback} /> + <Typography variant="h6"> or </Typography> + <AttachLink callback={childrenCallback} /> + </div> ); const Media = ( - <CardMedia - image={url} - className={classes.media} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} - > - {isMediaHover && <CancelOutlinedIcon className={classes.clearIcon} />} - </CardMedia> + <CardActionArea onClick={handleClear} className={classes.root}> + <CardMedia image={url} className={classes.media}> + <ClearIcon className={classes.clearIcon} /> + </CardMedia> + </CardActionArea> ); - return ( - <> - <CardActionArea onClick={handleClick} className={classes.root}> - {file ? (url && Media) : Upload} - </CardActionArea> - </> - ); + return url ? Media : Upload; }; export default PollCreationImage; diff --git a/src/containers/Profile/ProfileInfo.tsx b/src/containers/Profile/ProfileInfo.tsx index 87af99d..82f640d 100644 --- a/src/containers/Profile/ProfileInfo.tsx +++ b/src/containers/Profile/ProfileInfo.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Badge, Typography } from '@material-ui/core/'; import { makeStyles } from '@material-ui/core/styles'; import { User } from 'which-types'; @@ -6,7 +6,7 @@ import CameraAltIcon from '@material-ui/icons/CameraAlt'; import VerifiedIcon from '@material-ui/icons/CheckCircleOutline'; import Skeleton from '@material-ui/lab/Skeleton'; import Highlight from './Highlight'; -import UploadImage from '../../components/UploadImage/UploadImage'; +import AttachLink from '../../components/AttachLink/AttachLink'; import Avatar from '../../components/Avatar/Avatar'; import { patch } from '../../requests'; import { useAuth } from '../../hooks/useAuth'; @@ -86,14 +86,9 @@ const ProfileInfo: React.FC<PropTypes> = ({ savedPolls, totalVotes, setUserInfo, userInfo }) => { const classes = useStyles(); - const [input, setInput] = useState(false); const { user } = useAuth(); const dateSince = new Date(userInfo?.createdAt || '').toLocaleDateString(); - const handleClick = () => { - setInput(!input); - }; - const patchAvatar = (url: string) => { const id = user?._id; patch(`/users/${id}`, { avatarUrl: url }).then(res => { @@ -108,7 +103,7 @@ const ProfileInfo: React.FC<PropTypes> = ({ ? <Skeleton animation="wave" variant="circle" width={150} height={150} className={classes.avatar} /> : userInfo?._id === user?._id ? ( - <div> + <AttachLink callback={patchAvatar}> <div className={classes.avatarContainer}> <Badge overlap="circle" @@ -116,7 +111,6 @@ const ProfileInfo: React.FC<PropTypes> = ({ vertical: 'bottom', horizontal: 'right' }} - onClick={handleClick} badgeContent={( <div className={classes.badge}> <CameraAltIcon /> @@ -126,8 +120,7 @@ const ProfileInfo: React.FC<PropTypes> = ({ <Avatar className={classes.avatar} user={userInfo} /> </Badge> </div> - <UploadImage isOpen={input} setIsOpen={setInput} callback={patchAvatar} /> - </div> + </AttachLink> ) : <Avatar className={classes.avatar} user={userInfo} /> } diff --git a/src/index.tsx b/src/index.tsx index 1f70100..6c33db4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,7 +7,6 @@ import teal from '@material-ui/core/colors/teal'; import 'typeface-roboto'; import Header from './components/Header/Header'; -import ScrollTopArrow from './components/ScrollTopArrow/ScrollTopArrow'; import Page from './containers/Page/Page'; import { AuthProvider } from './hooks/useAuth'; @@ -33,7 +32,6 @@ const App: React.FC = () => { <CssBaseline /> <Header /> <Page /> - <ScrollTopArrow /> </ThemeProvider> </AuthProvider> </BrowserRouter> |