Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In amazon s3 #55

Merged
merged 10 commits into from
Jun 16, 2021
5 changes: 4 additions & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from './context/useAuthContext';
import { SocketProvider } from './context/useSocketContext';
import { SnackBarProvider } from './context/useSnackbarContext';
import { UserProvider } from './context/useUserContext';
import Routes from './Routes';

import './App.css';
Expand All @@ -15,7 +16,9 @@ function App(): JSX.Element {
<SnackBarProvider>
<AuthProvider>
<SocketProvider>
<Routes />
<UserProvider>
<Routes />
</UserProvider>
</SocketProvider>
</AuthProvider>
</SnackBarProvider>
Expand Down
1 change: 0 additions & 1 deletion client/src/components/EditProfile/EditProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useState } from 'react';
import TextField from '@material-ui/core/TextField';
import InputLabel from '@material-ui/core/InputLabel';
import Grid from '@material-ui/core/Grid';
Expand Down
115 changes: 95 additions & 20 deletions client/src/components/ProfilePhoto/ProfilePhoto.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import { useState, ChangeEvent } from 'react';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import CardHeader from '@material-ui/core/CardHeader';
Expand All @@ -7,48 +7,123 @@ import CardMedia from '@material-ui/core/CardMedia';
import Typography from '@material-ui/core/Typography';
import DeleteOutlineIcon from '@material-ui/icons/DeleteOutline';
import { useAuth } from '../../context/useAuthContext';
import { useSnackBar } from '../../context/useSnackbarContext';
import { useUser } from '../../context/useUserContext';

import useStyles from './useStyles';
import uploadImagesAPI from '../../helpers/APICalls/uploadImages';

interface UploadImagesState {
background: string | File;
profile: string | File;
}

const ProfilePhoto = (): JSX.Element => {
const [imgSrc, setImgSrc] = useState<string>('');
const [uploadImages, setUploadImages] = useState<UploadImagesState>({
background: '',
profile: '',
});
const { userState, dispatchUserContext } = useUser();
const classes = useStyles();
const { loggedInUser } = useAuth();

const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files?.length) {
const src = URL.createObjectURL(event.target.files[0]);
setImgSrc(src);
const { updateSnackBarMessage } = useSnackBar();
const handleImageUpload = (event: ChangeEvent<HTMLInputElement>): void => {
if (userState.background === '' && event.target.files?.length) {

Choose a reason for hiding this comment

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

maybe store event.target.files in a variable - you're accessing it multiple times in this block

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure, will do that.

const background = URL.createObjectURL(event.target.files[0]);
dispatchUserContext({ type: 'UPLOAD_BACKGROUND', background });
// eslint-disable-next-line

Choose a reason for hiding this comment

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

what is currently the eslint issue here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Forbidden non-null assertion @typescript-eslint/no-non-null-assertion

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is resolved when I store event.target.files in a variable.

setUploadImages((prevState) => ({ ...prevState, background: event.target.files![0] }));
} else if (userState.profile === '' && event.target.files?.length) {
const profile = URL.createObjectURL(event.target.files[0]);
dispatchUserContext({ type: 'UPLOAD_PROFILE', profile });
// eslint-disable-next-line
setUploadImages((prevState) => ({ ...prevState, profile: event.target.files![0] }));
}
};

const handleDeleteIcon = (): void => {
dispatchUserContext({ type: 'EMPTY_IMAGES' });
setUploadImages({
background: '',
profile: '',
});
};

const handleImageUploads = (): void => {
if (!uploadImages.background && !uploadImages.profile) return;
const formData = new FormData();
formData.set('background', uploadImages.background);
formData.set('profile', uploadImages.profile);
uploadImagesAPI(formData).then((data): void => {
console.log(data);
if (data.error) {
updateSnackBarMessage(data.error);
} else if (data.success) {
updateSnackBarMessage(data.success);
} else {
// should not get here from backend but this catch is for an unknown issue
console.error({ data });
updateSnackBarMessage('An unexpected error occurred. Please try again');
}
});
};

return (
<>
<CardHeader title="Profile Photo" component="h1" className={classes.cardHeader} />
<Box className={classes.mediaContainer}>
<CardMedia
className={classes.backgroundMedia}
component="img"
src={
userState.background
? userState.background
: `https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRmFJpMlEV-41ZFT8U7iUsMJzaXVIL_hAtT9A&usqp=CAU`
}
title="Background Picture"
alt="Your Background"
/>
<CardMedia
className={classes.media}
component="img"
src={imgSrc ? imgSrc : `https://robohash.org/${loggedInUser?.email}.png`}
src={userState.profile ? userState.profile : `https://robohash.org/${loggedInUser?.email}.png`}
title="User Profile Picture"
alt="Your Profile"
/>
<Typography color="textSecondary">
Be sure to use a photo that
<Box component="span" display="block">
clearly shows your face
</Box>
</Typography>
</Box>
<Typography color="textSecondary" className={classes.profileMediaInfo}>
Be sure to use a photo that
<Box component="span" display="block">
clearly shows your face
</Box>
</Typography>
<CardContent className={classes.cardContent}>
<Button variant="outlined" component="label" color="primary" className={classes.upload}>
Upload a file from your device
<input type="file" hidden accept="image/*" onChange={handleImageUpload} />
</Button>
<Button type="button" disableFocusRipple disableRipple>
<Box>
<Button variant="outlined" component="label" color="primary" className={classes.upload}>
{userState.background === ''
? `Select Your Background Image`
: userState.profile === ''
? `Select Your Profile Image`
: `Both Files are uploaded.`}
<input type="file" hidden accept="image/*" onChange={handleImageUpload} />
</Button>
</Box>
<Button type="button" disableFocusRipple disableRipple onClick={handleDeleteIcon}>
<DeleteOutlineIcon />
<Typography color="textSecondary">Delete photo</Typography>
<Typography color="textSecondary">Delete photos</Typography>
</Button>
<Box textAlign="center">
<Button
type="submit"
size="large"
variant="contained"
color="primary"
className={classes.submit}
onClick={handleImageUploads}
>
Save
</Button>
</Box>
</CardContent>
</>
);
Expand Down
21 changes: 21 additions & 0 deletions client/src/components/ProfilePhoto/useStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const useStyles = makeStyles((theme) => ({
alignItems: 'center',
},
mediaContainer: {
position: 'relative',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
Expand All @@ -24,10 +25,20 @@ const useStyles = makeStyles((theme) => ({
marginTop: '1rem',
},
},
backgroundMedia: {
maxHeight: '300px',
},
media: {
position: 'absolute',
width: '25%',
bottom: '-23%',
border: '4px solid white',
borderRadius: '6rem',
},
profileMediaInfo: {
textAlign: 'center',
marginTop: '5rem',
},
upload: {
margin: theme.spacing(2, 2, 2),
padding: 10,
Expand All @@ -37,6 +48,16 @@ const useStyles = makeStyles((theme) => ({
fontWeight: 'bold',
fontSize: '0.8rem',
},
submit: {
margin: theme.spacing(3, 2, 2),
padding: 10,
width: 160,
height: 56,
borderRadius: theme.shape.borderRadius,
marginTop: 49,
textTransform: 'uppercase',
fontWeight: 'bold',
},
}));

export default useStyles;
38 changes: 38 additions & 0 deletions client/src/context/useUserContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useReducer, useContext, createContext, FunctionComponent } from 'react';

interface IUserContext {
background: string;
profile: string;
}

type Action =
| { type: 'UPLOAD_PROFILE'; profile: string }
| { type: 'UPLOAD_BACKGROUND'; background: string }
| { type: 'EMPTY_IMAGES' };

const userReducer = (state: IUserContext, action: Action) => {
switch (action.type) {
case 'UPLOAD_PROFILE':
return { ...state, profile: action.profile };
case 'UPLOAD_BACKGROUND':
return { ...state, background: action.background };
case 'EMPTY_IMAGES':
return { ...state, background: '', profile: '' };
default:
throw new Error();
}
};

export const UserContext = createContext({});

export const UserProvider: FunctionComponent = ({ children }): JSX.Element => {
const [userState, dispatchUserContext] = useReducer(userReducer, {
background: '',
profile: '',
});
return <UserContext.Provider value={{ userState, dispatchUserContext }}>{children}</UserContext.Provider>;
};

export function useUser(): any {
return useContext(UserContext);
}
20 changes: 20 additions & 0 deletions client/src/helpers/APICalls/uploadImages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { S3UploadAPIData } from '../../interface/S3Upload';
import { FetchOptions } from '../../interface/FetchOptions';

const uploadImagesAPI = async (data: FormData): Promise<S3UploadAPIData> => {
const fetchOptions: FetchOptions = {
method: 'POST',
headers: {
Accept: 'application/json',
},
body: data,
credentials: 'include',
};
return await fetch(`/upload/uploadimage`, fetchOptions)
.then((res) => res.json())
.catch(() => ({
error: { message: 'Unable to connect to server. Please try again' },
}));
};

export default uploadImagesAPI;
13 changes: 7 additions & 6 deletions client/src/interface/FetchOptions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export interface FetchOptions {
method: string;
headers?: {
'Content-Type': string;
};
body?: string;
credentials: RequestCredentials;
method: string;
headers?: {
Accept?: string;
"Content-Type"?: string;
};
body?: string | FormData;
credentials: RequestCredentials;
}
5 changes: 5 additions & 0 deletions client/src/interface/S3Upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface S3UploadAPIData {
error?: string;
success?: string;
locationURls: { locationUrl: string; key: string; }[];
}
Loading