├── README.md
├── package.json
├── public
└── index.html
├── src
├── App.js
├── components
│ ├── Loading.js
│ ├── MainNotification.js
│ ├── Modal.js
│ ├── Nav.js
│ ├── Notify.js
│ ├── crop
│ │ ├── CropEasy.js
│ │ └── utils
│ │ │ └── cropImage.js
│ ├── imagesList
│ │ ├── ImagesList.js
│ │ └── Options.js
│ ├── upload
│ │ ├── Form.js
│ │ ├── Upload.js
│ │ └── progressList
│ │ │ ├── CircularProgressWithLabel.js
│ │ │ ├── ProgressItem.js
│ │ │ └── ProgressList.js
│ └── user
│ │ ├── Login.js
│ │ ├── Profile.js
│ │ ├── ResetPassword.js
│ │ ├── Verification.js
│ │ ├── inputs
│ │ ├── EmailField.js
│ │ ├── PasswordField.js
│ │ └── SubmitButton.js
│ │ └── settings
│ │ ├── AccountSettings.js
│ │ ├── ChangeEmail.js
│ │ ├── ChangePassword.js
│ │ ├── DeleteAccount.js
│ │ └── ReAuth.js
├── context
│ └── AuthContext.js
├── firebase
│ ├── addDocument.js
│ ├── config.js
│ ├── deleteDocument.js
│ ├── deleteFile.js
│ ├── deleteUserFiles.js
│ ├── updateUserRecords.js
│ ├── uploadFile.js
│ ├── uploadFileProgress.js
│ └── useFirestore.js
├── img
│ └── profile.jpeg
└── index.js
└── webpack.config.js
/README.md:
--------------------------------------------------------------------------------
1 | # React Firebase Images Gallery Project
2 |
3 | This project was created for [Youtube Lessons](https://www.youtube.com/watch?v=74rGC1e77tw&list=PLufbXXGswL_qTK6wu3yxprYXL42y9Nvh-).
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-firebase-gallery",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.7.1",
7 | "@emotion/styled": "^11.6.0",
8 | "@mui/icons-material": "^5.2.5",
9 | "@mui/material": "^5.2.5",
10 | "@testing-library/jest-dom": "^5.16.1",
11 | "@testing-library/react": "^12.1.2",
12 | "@testing-library/user-event": "^13.5.0",
13 | "firebase": "^9.6.1",
14 | "moment": "^2.29.1",
15 | "process": "^0.11.10",
16 | "react": "^17.0.2",
17 | "react-dom": "^17.0.2",
18 | "react-easy-crop": "^4.3.0",
19 | "react-image-lightbox": "^5.1.4",
20 | "react-scripts": "5.0.0",
21 | "uuid": "^8.3.2",
22 | "web-vitals": "^2.1.2"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject",
29 | "build:deploy": "env-cmd -f .env.local npm run build && firebase deploy -P react-firebase-gallery-develop"
30 | },
31 | "eslintConfig": {
32 | "extends": [
33 | "react-app",
34 | "react-app/jest"
35 | ]
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | },
49 | "devDependencies": {
50 | "env-cmd": "^10.1.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 | React Firebase Gallery
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import ImagesList from './components/imagesList/ImagesList';
2 | import Nav from './components/Nav';
3 | import Upload from './components/upload/Upload';
4 | import { Container } from '@mui/material';
5 | import AuthContext from './context/AuthContext';
6 | import Modal from './components/Modal';
7 | import MainNotification from './components/MainNotification';
8 | import Loading from './components/Loading';
9 | import Verification from './components/user/Verification';
10 |
11 | function App() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | export default App;
28 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import { Backdrop, CircularProgress } from '@mui/material';
2 | import { useAuth } from '../context/AuthContext';
3 |
4 | const Loading = () => {
5 | const { loading } = useAuth();
6 | return (
7 | theme.zIndex.drawer + 999 }}
10 | >
11 |
12 |
13 | );
14 | };
15 |
16 | export default Loading;
17 |
--------------------------------------------------------------------------------
/src/components/MainNotification.js:
--------------------------------------------------------------------------------
1 | import { useAuth } from '../context/AuthContext';
2 | import Notify from './Notify';
3 |
4 | const MainNotification = () => {
5 | const {
6 | alert: { location },
7 | } = useAuth();
8 | return location === 'main' && ;
9 | };
10 |
11 | export default MainNotification;
12 |
--------------------------------------------------------------------------------
/src/components/Modal.js:
--------------------------------------------------------------------------------
1 | import { Close } from '@mui/icons-material';
2 | import { Dialog, DialogTitle, IconButton } from '@mui/material';
3 | import { useEffect } from 'react';
4 | import { useAuth } from '../context/AuthContext';
5 | import Notify from './Notify';
6 |
7 | const Modal = () => {
8 | const {
9 | modal,
10 | setModal,
11 | alert: { location, isAlert },
12 | setAlert,
13 | } = useAuth();
14 |
15 | const handleClose = () => {
16 | setModal({ ...modal, isOpen: false });
17 | };
18 |
19 | useEffect(() => {
20 | if (modal.isOpen === false) {
21 | if (isAlert && location === 'modal') {
22 | setAlert({ ...alert, isAlert: false });
23 | }
24 | }
25 | }, [modal?.isOpen]);
26 | return (
27 |
46 | );
47 | };
48 |
49 | export default Modal;
50 |
--------------------------------------------------------------------------------
/src/components/Nav.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Box from '@mui/material/Box';
3 | import Avatar from '@mui/material/Avatar';
4 | import Menu from '@mui/material/Menu';
5 | import MenuItem from '@mui/material/MenuItem';
6 | import ListItemIcon from '@mui/material/ListItemIcon';
7 | import Divider from '@mui/material/Divider';
8 | import IconButton from '@mui/material/IconButton';
9 | import Tooltip from '@mui/material/Tooltip';
10 | import Settings from '@mui/icons-material/Settings';
11 | import Logout from '@mui/icons-material/Logout';
12 | import { Button } from '@mui/material';
13 | import { Lock } from '@mui/icons-material';
14 | import { useAuth } from '../context/AuthContext';
15 | import Login from './user/Login';
16 | import Profile from './user/Profile';
17 | import AccountSettings from './user/settings/AccountSettings';
18 |
19 | export default function Nav() {
20 | const [anchorEl, setAnchorEl] = React.useState(null);
21 | const open = Boolean(anchorEl);
22 | const handleClick = (event) => {
23 | setAnchorEl(event.currentTarget);
24 | };
25 | const handleClose = () => {
26 | setAnchorEl(null);
27 | };
28 |
29 | const { currentUser, setModal, logout, setAlert } = useAuth();
30 |
31 | const openLogin = () => {
32 | setModal({ isOpen: true, title: 'Login', content: });
33 | };
34 |
35 | const handleLogout = async () => {
36 | try {
37 | await logout();
38 | } catch (error) {
39 | setAlert({
40 | isAlert: true,
41 | severity: 'error',
42 | message: error.message,
43 | timeout: 8000,
44 | location: 'main',
45 | });
46 | console.log(error);
47 | }
48 | };
49 | return (
50 |
51 |
52 | {!currentUser ? (
53 | } onClick={openLogin}>
54 | Login
55 |
56 | ) : (
57 |
58 |
59 |
63 | {currentUser?.displayName?.charAt(0)?.toUpperCase() ||
64 | currentUser?.email?.charAt(0)?.toUpperCase()}
65 |
66 |
67 |
68 | )}
69 |
70 |
137 |
138 | );
139 | }
140 |
--------------------------------------------------------------------------------
/src/components/Notify.js:
--------------------------------------------------------------------------------
1 | import { Close } from '@mui/icons-material';
2 | import { Alert, Box, Collapse, IconButton } from '@mui/material';
3 | import { useEffect } from 'react';
4 | import { useRef } from 'react';
5 | import { useAuth } from '../context/AuthContext';
6 |
7 | const Notify = () => {
8 | const alertRef = useRef();
9 | const {
10 | alert: { isAlert, severity, message, timeout },
11 | setAlert,
12 | } = useAuth();
13 |
14 | useEffect(() => {
15 | alertRef.current.scrollIntoView({
16 | behavior: 'smooth',
17 | block: 'end',
18 | inline: 'nearest',
19 | });
20 |
21 | let timer;
22 | if (timeout) {
23 | timer = setTimeout(() => {
24 | setAlert({ ...alert, isAlert: false });
25 | }, timeout);
26 | }
27 | return () => clearTimeout(timer);
28 | }, [timeout]);
29 |
30 | return (
31 |
32 |
33 | setAlert({ ...alert, isAlert: false })}
40 | >
41 |
42 |
43 | }
44 | >
45 | {message}
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default Notify;
53 |
--------------------------------------------------------------------------------
/src/components/crop/CropEasy.js:
--------------------------------------------------------------------------------
1 | import { Cancel } from '@mui/icons-material';
2 | import CropIcon from '@mui/icons-material/Crop';
3 | import {
4 | Box,
5 | Button,
6 | DialogActions,
7 | DialogContent,
8 | Slider,
9 | Typography,
10 | } from '@mui/material';
11 | import React, { useState } from 'react';
12 | import Cropper from 'react-easy-crop';
13 | import { useAuth } from '../../context/AuthContext';
14 | import getCroppedImg from './utils/cropImage';
15 |
16 | const CropEasy = ({ photoURL, setOpenCrop, setPhotoURL, setFile }) => {
17 | const { setAlert, setLoading } = useAuth();
18 | const [crop, setCrop] = useState({ x: 0, y: 0 });
19 | const [zoom, setZoom] = useState(1);
20 | const [rotation, setRotation] = useState(0);
21 | const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
22 |
23 | const cropComplete = (croppedArea, croppedAreaPixels) => {
24 | setCroppedAreaPixels(croppedAreaPixels);
25 | };
26 |
27 | const cropImage = async () => {
28 | setLoading(true);
29 | try {
30 | const { file, url } = await getCroppedImg(
31 | photoURL,
32 | croppedAreaPixels,
33 | rotation
34 | );
35 | setPhotoURL(url);
36 | setFile(file);
37 | setOpenCrop(false);
38 | } catch (error) {
39 | setAlert({
40 | isAlert: true,
41 | severity: 'error',
42 | message: error.message,
43 | timeout: 5000,
44 | location: 'modal',
45 | });
46 | console.log(error);
47 | }
48 |
49 | setLoading(false);
50 | };
51 | return (
52 | <>
53 |
63 |
74 |
75 |
76 |
77 |
78 | Zoom: {zoomPercent(zoom)}
79 | setZoom(zoom)}
87 | />
88 |
89 |
90 | Rotation: {rotation + '°'}
91 | setRotation(rotation)}
97 | />
98 |
99 |
100 |
107 | }
110 | onClick={() => setOpenCrop(false)}
111 | >
112 | Cancel
113 |
114 | }
117 | onClick={cropImage}
118 | >
119 | Crop
120 |
121 |
122 |
123 | >
124 | );
125 | };
126 |
127 | export default CropEasy;
128 |
129 | const zoomPercent = (value) => {
130 | return `${Math.round(value * 100)}%`;
131 | };
132 |
--------------------------------------------------------------------------------
/src/components/crop/utils/cropImage.js:
--------------------------------------------------------------------------------
1 | export const createImage = (url) =>
2 | new Promise((resolve, reject) => {
3 | const image = new Image();
4 | image.addEventListener('load', () => resolve(image));
5 | image.addEventListener('error', (error) => reject(error));
6 | image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
7 | image.src = url;
8 | });
9 |
10 | export function getRadianAngle(degreeValue) {
11 | return (degreeValue * Math.PI) / 180;
12 | }
13 |
14 | /**
15 | * Returns the new bounding area of a rotated rectangle.
16 | */
17 | export function rotateSize(width, height, rotation) {
18 | const rotRad = getRadianAngle(rotation);
19 |
20 | return {
21 | width:
22 | Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
23 | height:
24 | Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
25 | };
26 | }
27 |
28 | /**
29 | * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
30 | */
31 | export default async function getCroppedImg(
32 | imageSrc,
33 | pixelCrop,
34 | rotation = 0,
35 | flip = { horizontal: false, vertical: false }
36 | ) {
37 | const image = await createImage(imageSrc);
38 | const canvas = document.createElement('canvas');
39 | const ctx = canvas.getContext('2d');
40 |
41 | if (!ctx) {
42 | return null;
43 | }
44 |
45 | const rotRad = getRadianAngle(rotation);
46 |
47 | // calculate bounding box of the rotated image
48 | const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
49 | image.width,
50 | image.height,
51 | rotation
52 | );
53 |
54 | // set canvas size to match the bounding box
55 | canvas.width = bBoxWidth;
56 | canvas.height = bBoxHeight;
57 |
58 | // translate canvas context to a central location to allow rotating and flipping around the center
59 | ctx.translate(bBoxWidth / 2, bBoxHeight / 2);
60 | ctx.rotate(rotRad);
61 | ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);
62 | ctx.translate(-image.width / 2, -image.height / 2);
63 |
64 | // draw rotated image
65 | ctx.drawImage(image, 0, 0);
66 |
67 | // croppedAreaPixels values are bounding box relative
68 | // extract the cropped image using these values
69 | const data = ctx.getImageData(
70 | pixelCrop.x,
71 | pixelCrop.y,
72 | pixelCrop.width,
73 | pixelCrop.height
74 | );
75 |
76 | // set canvas width to final desired crop size - this will clear existing context
77 | canvas.width = pixelCrop.width;
78 | canvas.height = pixelCrop.height;
79 |
80 | // paste generated rotate image at the top left corner
81 | ctx.putImageData(data, 0, 0);
82 |
83 | // As Base64 string
84 | // return canvas.toDataURL('image/jpeg');
85 |
86 | // As a blob
87 | return new Promise((resolve, reject) => {
88 | canvas.toBlob((file) => {
89 | file.name = 'cropped.jpeg';
90 | resolve({ file: file, url: URL.createObjectURL(file) });
91 | }, 'image/jpeg');
92 | });
93 | }
94 |
--------------------------------------------------------------------------------
/src/components/imagesList/ImagesList.js:
--------------------------------------------------------------------------------
1 | import ImageList from '@mui/material/ImageList';
2 | import ImageListItem from '@mui/material/ImageListItem';
3 |
4 | import { Avatar, Tooltip, Typography } from '@mui/material';
5 | import moment from 'moment';
6 | import Options from './Options';
7 | import useFirestore from '../../firebase/useFirestore';
8 | import Lightbox from 'react-image-lightbox';
9 | import 'react-image-lightbox/style.css';
10 | import { useState } from 'react';
11 |
12 | function srcset(image, size, rows = 1, cols = 1) {
13 | return {
14 | src: `${image}?w=${size * cols}&h=${size * rows}&fit=crop&auto=format`,
15 | srcSet: `${image}?w=${size * cols}&h=${
16 | size * rows
17 | }&fit=crop&auto=format&dpr=2 2x`,
18 | };
19 | }
20 |
21 | export default function ImagesList() {
22 | const { documents } = useFirestore('gallery');
23 | const [isOpen, setIsOpen] = useState(false);
24 | const [photoIndex, setPhotoIndex] = useState(0);
25 | return (
26 | <>
27 |
28 | {documents.map((item, index) => (
29 |
48 |
53 |
54 |
{
68 | setPhotoIndex(index);
69 | setIsOpen(true);
70 | }}
71 | />
72 |
85 | {moment(item?.data?.timestamp?.toDate()).fromNow()}
86 |
87 |
95 |
99 |
100 |
101 | ))}
102 |
103 | {isOpen && (
104 | setIsOpen(false)}
114 | onMoveNextRequest={() =>
115 | setPhotoIndex((photoIndex + 1) % documents.length)
116 | }
117 | onMovePrevRequest={() =>
118 | setPhotoIndex(
119 | (photoIndex + documents.length - 1) % documents.length
120 | )
121 | }
122 | imageTitle={documents[photoIndex]?.data?.uName}
123 | imageCaption={documents[photoIndex]?.data?.uEmail}
124 | />
125 | )}
126 | >
127 | );
128 | }
129 |
130 | const pattern = [
131 | {
132 | rows: 2,
133 | cols: 2,
134 | },
135 | {
136 | rows: 1,
137 | cols: 1,
138 | },
139 | {
140 | rows: 1,
141 | cols: 1,
142 | },
143 | {
144 | rows: 1,
145 | cols: 2,
146 | },
147 | {
148 | rows: 1,
149 | cols: 2,
150 | },
151 | {
152 | rows: 2,
153 | cols: 2,
154 | },
155 | {
156 | rows: 1,
157 | cols: 1,
158 | },
159 | {
160 | rows: 1,
161 | cols: 1,
162 | },
163 | ];
164 |
--------------------------------------------------------------------------------
/src/components/imagesList/Options.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Box from '@mui/material/Box';
3 | import Menu from '@mui/material/Menu';
4 | import MenuItem from '@mui/material/MenuItem';
5 | import ListItemIcon from '@mui/material/ListItemIcon';
6 | import Tooltip from '@mui/material/Tooltip';
7 | import { IconButton } from '@mui/material';
8 | import { Delete, Download, MoreVert } from '@mui/icons-material';
9 | import deleteDocument from '../../firebase/deleteDocument';
10 | import deleteFile from '../../firebase/deleteFile';
11 | import { useAuth } from '../../context/AuthContext';
12 |
13 | export default function Options({ imageId, uid, imageURL }) {
14 | const [anchorEl, setAnchorEl] = React.useState(null);
15 | const open = Boolean(anchorEl);
16 | const { currentUser, setAlert } = useAuth();
17 | const handleClick = (event) => {
18 | setAnchorEl(event.currentTarget);
19 | };
20 | const handleClose = () => {
21 | setAnchorEl(null);
22 | };
23 | const handleDelete = async () => {
24 | try {
25 | await deleteDocument('gallery', imageId);
26 | await deleteFile(`gallery/${currentUser.uid}/${imageId}`);
27 | } catch (error) {
28 | setAlert({
29 | isAlert: true,
30 | severity: 'error',
31 | message: error.message,
32 | timeout: 8000,
33 | location: 'main',
34 | });
35 | console.log(error);
36 | }
37 | };
38 |
39 | const handleDownload = async () => {
40 | try {
41 | const response = await fetch(imageURL);
42 | const data = await response.blob();
43 | const blob = URL.createObjectURL(data);
44 | const link = document.createElement('a');
45 | link.href = blob;
46 | link.download = imageId;
47 | link.click();
48 | URL.revokeObjectURL(blob);
49 | link.remove();
50 | } catch (error) {
51 | setAlert({
52 | isAlert: true,
53 | severity: 'error',
54 | message: error.message,
55 | timeout: 8000,
56 | location: 'main',
57 | });
58 | console.log(error);
59 | }
60 | };
61 | return (
62 |
63 |
64 |
65 |
75 |
76 |
77 |
78 |
79 |
128 |
129 | );
130 | }
131 |
--------------------------------------------------------------------------------
/src/components/upload/Form.js:
--------------------------------------------------------------------------------
1 | import { Add } from '@mui/icons-material';
2 | import { Fab, Input } from '@mui/material';
3 | import { useRef } from 'react';
4 | import { useAuth } from '../../context/AuthContext';
5 | import Login from '../user/Login';
6 |
7 | const Form = ({ setFiles }) => {
8 | const { currentUser, setModal } = useAuth();
9 | const fileRef = useRef();
10 |
11 | const handleClick = () => {
12 | if (!currentUser) {
13 | return setModal({ isOpen: true, title: 'Login', content: });
14 | }
15 |
16 | fileRef.current.click();
17 | };
18 |
19 | const handleChange = (e) => {
20 | setFiles([...e.target.files]);
21 | fileRef.current.value = null;
22 | };
23 | return (
24 |
36 | );
37 | };
38 |
39 | export default Form;
40 |
--------------------------------------------------------------------------------
/src/components/upload/Upload.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Form from './Form';
3 | import ProgressList from './progressList/ProgressList';
4 |
5 | const Upload = () => {
6 | const [files, setFiles] = useState([]);
7 | return (
8 |
12 | );
13 | };
14 |
15 | export default Upload;
16 |
--------------------------------------------------------------------------------
/src/components/upload/progressList/CircularProgressWithLabel.js:
--------------------------------------------------------------------------------
1 | import { Box, CircularProgress, Typography } from '@mui/material';
2 | import React from 'react';
3 |
4 | const CircularProgressWithLabel = ({ value }) => {
5 | return (
6 |
7 |
13 |
25 |
31 | {Math.round(value) + '%'}
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default CircularProgressWithLabel;
39 |
--------------------------------------------------------------------------------
/src/components/upload/progressList/ProgressItem.js:
--------------------------------------------------------------------------------
1 | import { CheckCircleOutline } from '@mui/icons-material';
2 | import { Box, ImageListItem } from '@mui/material';
3 | import React, { useEffect, useState } from 'react';
4 | import CircularProgressWithLabel from './CircularProgressWithLabel';
5 | import { v4 as uuidv4 } from 'uuid';
6 | import uploadFileProgress from '../../../firebase/uploadFileProgress';
7 | import addDocument from '../../../firebase/addDocument';
8 | import { useAuth } from '../../../context/AuthContext';
9 |
10 | const ProgressItem = ({ file }) => {
11 | const [progress, setProgress] = useState(0);
12 | const [imageURL, setImageURL] = useState(null);
13 | const { currentUser, setAlert } = useAuth();
14 | useEffect(() => {
15 | const uploadImage = async () => {
16 | const imageName = uuidv4() + '.' + file.name.split('.').pop();
17 | try {
18 | const url = await uploadFileProgress(
19 | file,
20 | `gallery/${currentUser?.uid}`,
21 | imageName,
22 | setProgress
23 | );
24 | const galleryDoc = {
25 | imageURL: url,
26 | uid: currentUser?.uid || '',
27 | uEmail: currentUser?.email || '',
28 | uName: currentUser?.displayName || '',
29 | uPhoto: currentUser?.photoURL || '',
30 | };
31 | await addDocument('gallery', galleryDoc, imageName);
32 | setImageURL(null);
33 | } catch (error) {
34 | setAlert({
35 | isAlert: true,
36 | severity: 'error',
37 | message: error.message,
38 | timeout: 8000,
39 | location: 'main',
40 | });
41 | console.log(error);
42 | }
43 | };
44 | setImageURL(URL.createObjectURL(file));
45 | uploadImage();
46 | }, [file]);
47 | return (
48 | imageURL && (
49 |
50 |
51 |
52 | {progress < 100 ? (
53 |
54 | ) : (
55 |
58 | )}
59 |
60 |
61 | )
62 | );
63 | };
64 |
65 | export default ProgressItem;
66 |
67 | const backDrop = {
68 | position: 'absolute',
69 | top: 0,
70 | right: 0,
71 | bottom: 0,
72 | left: 0,
73 | display: 'flex',
74 | alignItems: 'center',
75 | justifyContent: 'center',
76 | background: 'rgba(0,0,0, .5)',
77 | };
78 |
--------------------------------------------------------------------------------
/src/components/upload/progressList/ProgressList.js:
--------------------------------------------------------------------------------
1 | import { ImageList } from '@mui/material';
2 | import React from 'react';
3 | import ProgressItem from './ProgressItem';
4 |
5 | const ProgressList = ({ files }) => {
6 | return (
7 |
8 | {files.map((file, index) => (
9 |
10 | ))}
11 |
12 | );
13 | };
14 |
15 | export default ProgressList;
16 |
--------------------------------------------------------------------------------
/src/components/user/Login.js:
--------------------------------------------------------------------------------
1 | import { Google } from '@mui/icons-material';
2 | import {
3 | Button,
4 | DialogActions,
5 | DialogContent,
6 | DialogContentText,
7 | } from '@mui/material';
8 | import { useEffect, useState } from 'react';
9 | import { useRef } from 'react';
10 | import { useAuth } from '../../context/AuthContext';
11 | import EmailField from './inputs/EmailField';
12 | import PasswordField from './inputs/PasswordField';
13 | import SubmitButton from './inputs/SubmitButton';
14 | import ResetPassword from './ResetPassword';
15 |
16 | const Login = () => {
17 | const emailRef = useRef();
18 | const passwordRef = useRef();
19 | const confirmPasswordRef = useRef();
20 |
21 | const [isRegister, setIsRegister] = useState(false);
22 | const {
23 | modal,
24 | setModal,
25 | signUp,
26 | login,
27 | loginWithGoogle,
28 | setAlert,
29 | setLoading,
30 | } = useAuth();
31 |
32 | const handleSubmit = async (e) => {
33 | e.preventDefault();
34 | setLoading(true);
35 | const email = emailRef.current.value;
36 | const password = passwordRef.current.value;
37 | if (isRegister) {
38 | const confirmPassword = confirmPasswordRef.current.value;
39 | try {
40 | if (password !== confirmPassword) {
41 | throw new Error("Passwords don't match");
42 | }
43 | await signUp(email, password);
44 | setModal({ ...modal, isOpen: false });
45 | } catch (error) {
46 | setAlert({
47 | isAlert: true,
48 | severity: 'error',
49 | message: error.message,
50 | timeout: 5000,
51 | location: 'modal',
52 | });
53 | console.log(error);
54 | }
55 | } else {
56 | try {
57 | await login(email, password);
58 | setModal({ ...modal, isOpen: false });
59 | } catch (error) {
60 | setAlert({
61 | isAlert: true,
62 | severity: 'error',
63 | message: error.message,
64 | timeout: 5000,
65 | location: 'modal',
66 | });
67 | console.log(error);
68 | }
69 | }
70 | setLoading(false);
71 | };
72 |
73 | const handleGoogleLogin = async () => {
74 | try {
75 | await loginWithGoogle();
76 | setModal({ ...modal, isOpen: false });
77 | } catch (error) {
78 | setAlert({
79 | isAlert: true,
80 | severity: 'error',
81 | message: error.message,
82 | timeout: 5000,
83 | location: 'modal',
84 | });
85 | console.log(error);
86 | }
87 | };
88 |
89 | useEffect(() => {
90 | if (isRegister) {
91 | setModal({ ...modal, title: 'Register' });
92 | } else {
93 | setModal({ ...modal, title: 'Login' });
94 | }
95 | }, [isRegister]);
96 | return (
97 | <>
98 |
132 |
133 | {isRegister
134 | ? 'Do you have an account? Sign in now'
135 | : "Don't you have an account? Create one now"}
136 |
139 |
140 |
141 | }
144 | onClick={handleGoogleLogin}
145 | >
146 | Login with Google
147 |
148 |
149 | >
150 | );
151 | };
152 |
153 | export default Login;
154 |
--------------------------------------------------------------------------------
/src/components/user/Profile.js:
--------------------------------------------------------------------------------
1 | import {
2 | Avatar,
3 | Box,
4 | DialogActions,
5 | DialogContent,
6 | DialogContentText,
7 | IconButton,
8 | TextField,
9 | } from '@mui/material';
10 | import { useState } from 'react';
11 | import { useAuth } from '../../context/AuthContext';
12 | import SubmitButton from './inputs/SubmitButton';
13 | import { v4 as uuidv4 } from 'uuid';
14 | import uploadFile from '../../firebase/uploadFile';
15 | import { updateProfile } from 'firebase/auth';
16 | import deleteFile from '../../firebase/deleteFile';
17 | import updateUserRecords from '../../firebase/updateUserRecords';
18 | import CropEasy from '../crop/CropEasy';
19 | import { Crop } from '@mui/icons-material';
20 | import { useEffect } from 'react';
21 |
22 | const Profile = () => {
23 | const { currentUser, setLoading, setAlert, modal, setModal } = useAuth();
24 | const [name, setName] = useState(currentUser?.displayName);
25 | const [file, setFile] = useState(null);
26 | const [photoURL, setPhotoURL] = useState(currentUser?.photoURL);
27 | const [openCrop, setOpenCrop] = useState(false);
28 |
29 | const handleChange = (e) => {
30 | const file = e.target.files[0];
31 | if (file) {
32 | setFile(file);
33 | setPhotoURL(URL.createObjectURL(file));
34 | setOpenCrop(true);
35 | }
36 | };
37 |
38 | const handleSubmit = async (e) => {
39 | e.preventDefault();
40 | setLoading(true);
41 |
42 | let userObj = { displayName: name };
43 | let imagesObj = { uName: name };
44 | try {
45 | if (file) {
46 | const imageName = uuidv4() + '.' + file?.name?.split('.')?.pop();
47 | const url = await uploadFile(
48 | file,
49 | `profile/${currentUser?.uid}/${imageName}`
50 | );
51 |
52 | if (currentUser?.photoURL) {
53 | const prevImage = currentUser?.photoURL
54 | ?.split(`${currentUser?.uid}%2F`)[1]
55 | .split('?')[0];
56 | if (prevImage) {
57 | try {
58 | await deleteFile(`profile/${currentUser?.uid}/${prevImage}`);
59 | } catch (error) {
60 | console.log(error);
61 | }
62 | }
63 | }
64 |
65 | userObj = { ...userObj, photoURL: url };
66 | imagesObj = { ...imagesObj, uPhoto: url };
67 | }
68 |
69 | await updateProfile(currentUser, userObj);
70 | await updateUserRecords('gallery', currentUser?.uid, imagesObj);
71 |
72 | setAlert({
73 | isAlert: true,
74 | severity: 'success',
75 | message: 'Your profile has been updated',
76 | timeout: 3000,
77 | location: 'modal',
78 | });
79 | } catch (error) {
80 | setAlert({
81 | isAlert: true,
82 | severity: 'error',
83 | message: error.message,
84 | timeout: 5000,
85 | location: 'modal',
86 | });
87 | console.log(error);
88 | }
89 |
90 | setLoading(false);
91 | };
92 |
93 | useEffect(() => {
94 | if (openCrop) {
95 | setModal({ ...modal, title: 'Crop Profile Photo' });
96 | } else {
97 | setModal({ ...modal, title: 'Update Profile' });
98 | }
99 | }, [openCrop]);
100 |
101 | return !openCrop ? (
102 |
147 | ) : (
148 |
149 | );
150 | };
151 |
152 | export default Profile;
153 |
--------------------------------------------------------------------------------
/src/components/user/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import { DialogActions, DialogContent, DialogContentText } from '@mui/material';
2 | import { useRef } from 'react';
3 | import { useAuth } from '../../context/AuthContext';
4 | import EmailField from './inputs/EmailField';
5 | import SubmitButton from './inputs/SubmitButton';
6 |
7 | const ResetPassword = () => {
8 | const { setLoading, setAlert, setModal, modal, resetPassword } = useAuth();
9 | const emailRef = useRef();
10 |
11 | const handleSubmit = async (e) => {
12 | e.preventDefault();
13 | setLoading(true);
14 | try {
15 | await resetPassword(emailRef.current.value);
16 | setModal({ ...modal, isOpen: false });
17 | setAlert({
18 | isAlert: true,
19 | severity: 'success',
20 | message: 'reset link has been sent to your email inbox',
21 | timeout: 8000,
22 | location: 'main',
23 | });
24 | } catch (error) {
25 | setAlert({
26 | isAlert: true,
27 | severity: 'error',
28 | message: error.message,
29 | timeout: 5000,
30 | location: 'modal',
31 | });
32 | }
33 | setLoading(false);
34 | };
35 |
36 | return (
37 |
46 | );
47 | };
48 |
49 | export default ResetPassword;
50 |
--------------------------------------------------------------------------------
/src/components/user/Verification.js:
--------------------------------------------------------------------------------
1 | import { Close } from '@mui/icons-material';
2 | import { Alert, Box, Button, Collapse, IconButton } from '@mui/material';
3 | import { sendEmailVerification } from 'firebase/auth';
4 | import { useState } from 'react';
5 | import { useAuth } from '../../context/AuthContext';
6 |
7 | const Verification = () => {
8 | const { currentUser, setAlert, setLoading } = useAuth();
9 | const [open, setOpen] = useState(true);
10 | const [isClicked, setIsClicked] = useState(false);
11 |
12 | const verify = async () => {
13 | setIsClicked(true);
14 | setLoading(true);
15 |
16 | try {
17 | await sendEmailVerification(currentUser);
18 | setAlert({
19 | isAlert: true,
20 | severity: 'info',
21 | message: 'verification link has been sent to your email inbox',
22 | timeout: 8000,
23 | location: 'main',
24 | });
25 | } catch (error) {
26 | setAlert({
27 | isAlert: true,
28 | severity: 'error',
29 | message: error.message,
30 | timeout: 8000,
31 | location: 'main',
32 | });
33 | console.log(error);
34 | }
35 |
36 | setLoading(false);
37 | };
38 | return (
39 | currentUser?.emailVerified === false && (
40 |
41 |
42 | setOpen(false)}
49 | >
50 |
51 |
52 | }
53 | sx={{ mb: 3 }}
54 | >
55 | Your email has not been verified yet!
56 |
64 |
65 |
66 |
67 | )
68 | );
69 | };
70 |
71 | export default Verification;
72 |
--------------------------------------------------------------------------------
/src/components/user/inputs/EmailField.js:
--------------------------------------------------------------------------------
1 | import { TextField } from '@mui/material';
2 |
3 | const EmailField = ({ emailRef, defaultValue = '' }) => {
4 | return (
5 |
17 | );
18 | };
19 |
20 | export default EmailField;
21 |
--------------------------------------------------------------------------------
/src/components/user/inputs/PasswordField.js:
--------------------------------------------------------------------------------
1 | import { Visibility, VisibilityOff } from '@mui/icons-material';
2 | import { IconButton, InputAdornment, TextField } from '@mui/material';
3 | import { useState } from 'react';
4 |
5 | const PasswordField = ({
6 | passwordRef,
7 | id = 'password',
8 | label = 'Password',
9 | autoFocus = true,
10 | }) => {
11 | const [showPassword, setShowPassword] = useState(false);
12 |
13 | const handleClick = () => {
14 | setShowPassword(!showPassword);
15 | };
16 | const handleMouseDown = (e) => {
17 | e.preventDefault();
18 | };
19 | return (
20 |
34 |
39 | {showPassword ? : }
40 |
41 |
42 | ),
43 | }}
44 | />
45 | );
46 | };
47 |
48 | export default PasswordField;
49 |
--------------------------------------------------------------------------------
/src/components/user/inputs/SubmitButton.js:
--------------------------------------------------------------------------------
1 | import { Send } from '@mui/icons-material';
2 | import { Button } from '@mui/material';
3 |
4 | const SubmitButton = () => {
5 | return (
6 | } type="submit">
7 | Submit
8 |
9 | );
10 | };
11 |
12 | export default SubmitButton;
13 |
--------------------------------------------------------------------------------
/src/components/user/settings/AccountSettings.js:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | DialogActions,
4 | DialogContent,
5 | DialogContentText,
6 | } from '@mui/material';
7 | import { GoogleAuthProvider, reauthenticateWithPopup } from 'firebase/auth';
8 | import { useAuth } from '../../../context/AuthContext';
9 | import ChangeEmail from './ChangeEmail';
10 | import DeleteAccount from './DeleteAccount';
11 | import ReAuth from './ReAuth';
12 |
13 | const AccountSettings = () => {
14 | const { currentUser, setModal, modal, setAlert } = useAuth();
15 | const isPasswordProvider =
16 | currentUser?.providerData[0].providerId === 'password';
17 |
18 | const handleAction = async (action) => {
19 | if (isPasswordProvider) {
20 | setModal({
21 | ...modal,
22 | title: 'Re-Login',
23 | content: ,
24 | });
25 | } else {
26 | try {
27 | await reauthenticateWithPopup(currentUser, new GoogleAuthProvider());
28 | switch (action) {
29 | case 'changeEmail':
30 | setModal({
31 | ...modal,
32 | title: 'Update Email',
33 | content: ,
34 | });
35 | break;
36 | case 'deleteAccount':
37 | setModal({
38 | ...modal,
39 | title: 'Delete Account',
40 | content: ,
41 | });
42 | break;
43 | default:
44 | throw new Error('No matching action');
45 | }
46 | } catch (error) {
47 | setAlert({
48 | isAlert: true,
49 | severity: 'error',
50 | message: error.message,
51 | timeout: 5000,
52 | location: 'modal',
53 | });
54 | console.log(error);
55 | }
56 | }
57 | };
58 | return (
59 | <>
60 |
61 |
62 | For security reason, you need to provide your credentials to do any of
63 | these actions:
64 |
65 |
66 |
67 | {isPasswordProvider && (
68 |
71 | )}
72 |
75 |
78 |
79 | >
80 | );
81 | };
82 |
83 | export default AccountSettings;
84 |
--------------------------------------------------------------------------------
/src/components/user/settings/ChangeEmail.js:
--------------------------------------------------------------------------------
1 | import { DialogActions, DialogContent, DialogContentText } from '@mui/material';
2 | import { updateEmail } from 'firebase/auth';
3 | import { useRef } from 'react';
4 | import { useAuth } from '../../../context/AuthContext';
5 | import EmailField from '../inputs/EmailField';
6 | import SubmitButton from '../inputs/SubmitButton';
7 |
8 | const ChangeEmail = () => {
9 | const { currentUser, setLoading, setAlert, setModal, modal } = useAuth();
10 | const emailRef = useRef();
11 |
12 | const handleSubmit = async (e) => {
13 | e.preventDefault();
14 | setLoading(true);
15 | try {
16 | await updateEmail(currentUser, emailRef.current.value);
17 | setModal({ ...modal, isOpen: false });
18 | setAlert({
19 | isAlert: true,
20 | severity: 'success',
21 | message: 'Your email has been updated',
22 | timeout: 8000,
23 | location: 'main',
24 | });
25 | } catch (error) {
26 | setAlert({
27 | isAlert: true,
28 | severity: 'error',
29 | message: error.message,
30 | timeout: 5000,
31 | location: 'modal',
32 | });
33 | console.log(error);
34 | }
35 | setLoading(false);
36 | };
37 |
38 | return (
39 |
48 | );
49 | };
50 |
51 | export default ChangeEmail;
52 |
--------------------------------------------------------------------------------
/src/components/user/settings/ChangePassword.js:
--------------------------------------------------------------------------------
1 | import { DialogActions, DialogContent, DialogContentText } from '@mui/material';
2 | import { updatePassword } from 'firebase/auth';
3 | import { useRef } from 'react';
4 | import { useAuth } from '../../../context/AuthContext';
5 | import PasswordField from '../inputs/PasswordField';
6 | import SubmitButton from '../inputs/SubmitButton';
7 |
8 | const ChangePassword = () => {
9 | const { currentUser, setLoading, setAlert, setModal, modal } = useAuth();
10 | const passwordRef = useRef();
11 | const confirmPasswordRef = useRef();
12 |
13 | const handleSubmit = async (e) => {
14 | e.preventDefault();
15 | setLoading(true);
16 | try {
17 | if (passwordRef.current.value !== confirmPasswordRef.current.value) {
18 | throw new Error('Passwords do not match');
19 | }
20 | await updatePassword(currentUser, passwordRef.current.value);
21 | setModal({ ...modal, isOpen: false });
22 | setAlert({
23 | isAlert: true,
24 | severity: 'success',
25 | message: 'Your password has been updated',
26 | timeout: 8000,
27 | location: 'main',
28 | });
29 | } catch (error) {
30 | setAlert({
31 | isAlert: true,
32 | severity: 'error',
33 | message: error.message,
34 | timeout: 5000,
35 | location: 'modal',
36 | });
37 | console.log(error);
38 | }
39 | setLoading(false);
40 | };
41 |
42 | return (
43 |
60 | );
61 | };
62 |
63 | export default ChangePassword;
64 |
--------------------------------------------------------------------------------
/src/components/user/settings/DeleteAccount.js:
--------------------------------------------------------------------------------
1 | import { Send } from '@mui/icons-material';
2 | import {
3 | Button,
4 | DialogActions,
5 | DialogContent,
6 | DialogContentText,
7 | } from '@mui/material';
8 | import { deleteUser } from 'firebase/auth';
9 | import { useAuth } from '../../../context/AuthContext';
10 | import deleteUserFiles from '../../../firebase/deleteUserFiles';
11 |
12 | const DeleteAccount = () => {
13 | const { currentUser, setLoading, setAlert, setModal, modal } = useAuth();
14 |
15 | const handleSubmit = async (e) => {
16 | e.preventDefault();
17 | setLoading(true);
18 | try {
19 | await deleteUserFiles('gallery', currentUser);
20 | await deleteUser(currentUser);
21 | setModal({ ...modal, isOpen: false });
22 | setAlert({
23 | isAlert: true,
24 | severity: 'success',
25 | message: 'Your account has been deleted',
26 | timeout: 8000,
27 | location: 'main',
28 | });
29 | } catch (error) {
30 | setAlert({
31 | isAlert: true,
32 | severity: 'error',
33 | message: error.message,
34 | timeout: 5000,
35 | location: 'modal',
36 | });
37 | console.log(error);
38 | }
39 | setLoading(false);
40 | };
41 |
42 | return (
43 |
56 | );
57 | };
58 |
59 | export default DeleteAccount;
60 |
--------------------------------------------------------------------------------
/src/components/user/settings/ReAuth.js:
--------------------------------------------------------------------------------
1 | import { DialogActions, DialogContent, DialogContentText } from '@mui/material';
2 | import { EmailAuthProvider, reauthenticateWithCredential } from 'firebase/auth';
3 | import { useRef } from 'react';
4 | import { useAuth } from '../../../context/AuthContext';
5 | import PasswordField from '../inputs/PasswordField';
6 | import SubmitButton from '../inputs/SubmitButton';
7 | import ChangeEmail from './ChangeEmail';
8 | import ChangePassword from './ChangePassword';
9 | import DeleteAccount from './DeleteAccount';
10 |
11 | const ReAuth = ({ action }) => {
12 | const { currentUser, setLoading, setAlert, setModal, modal } = useAuth();
13 | const passwordRef = useRef();
14 |
15 | const handleSubmit = async (e) => {
16 | e.preventDefault();
17 | setLoading(true);
18 | const credential = EmailAuthProvider.credential(
19 | currentUser?.email,
20 | passwordRef.current.value
21 | );
22 | try {
23 | await reauthenticateWithCredential(currentUser, credential);
24 |
25 | switch (action) {
26 | case 'changePassword':
27 | setModal({
28 | ...modal,
29 | title: 'Update Password',
30 | content: ,
31 | });
32 | break;
33 | case 'changeEmail':
34 | setModal({
35 | ...modal,
36 | title: 'Update Email',
37 | content: ,
38 | });
39 | break;
40 | case 'deleteAccount':
41 | setModal({
42 | ...modal,
43 | title: 'Delete Account',
44 | content: ,
45 | });
46 | break;
47 | default:
48 | throw new Error('No matching action');
49 | }
50 | } catch (error) {
51 | setAlert({
52 | isAlert: true,
53 | severity: 'error',
54 | message: error.message,
55 | timeout: 5000,
56 | location: 'modal',
57 | });
58 | console.log(error);
59 | }
60 | setLoading(false);
61 | };
62 |
63 | return (
64 |
75 | );
76 | };
77 |
78 | export default ReAuth;
79 |
--------------------------------------------------------------------------------
/src/context/AuthContext.js:
--------------------------------------------------------------------------------
1 | import {
2 | createUserWithEmailAndPassword,
3 | GoogleAuthProvider,
4 | onAuthStateChanged,
5 | sendPasswordResetEmail,
6 | signInWithEmailAndPassword,
7 | signInWithPopup,
8 | signOut,
9 | } from 'firebase/auth';
10 | import { useState } from 'react';
11 | import { useEffect } from 'react';
12 | import { useContext } from 'react';
13 | import { createContext } from 'react';
14 | import { auth } from '../firebase/config';
15 |
16 | const authContext = createContext();
17 |
18 | export const useAuth = () => {
19 | return useContext(authContext);
20 | };
21 |
22 | const AuthContext = ({ children }) => {
23 | const [currentUser, setCurrentUser] = useState(null);
24 | const [modal, setModal] = useState({ isOpen: false, title: '', content: '' });
25 | const [alert, setAlert] = useState({
26 | isAlert: false,
27 | severity: 'info',
28 | message: '',
29 | timeout: null,
30 | location: '',
31 | });
32 | const [loading, setLoading] = useState(false);
33 |
34 | const signUp = (email, password) => {
35 | return createUserWithEmailAndPassword(auth, email, password);
36 | };
37 | const login = (email, password) => {
38 | return signInWithEmailAndPassword(auth, email, password);
39 | };
40 | const loginWithGoogle = () => {
41 | const provider = new GoogleAuthProvider();
42 | return signInWithPopup(auth, provider);
43 | };
44 | const logout = () => {
45 | return signOut(auth);
46 | };
47 |
48 | const resetPassword = (email) => {
49 | return sendPasswordResetEmail(auth, email);
50 | };
51 |
52 | useEffect(() => {
53 | const unsubscribe = onAuthStateChanged(auth, (user) => {
54 | setCurrentUser(user);
55 | console.log('user status changed: ', user);
56 | });
57 | return unsubscribe;
58 | }, []);
59 |
60 | const value = {
61 | currentUser,
62 | signUp,
63 | login,
64 | logout,
65 | modal,
66 | setModal,
67 | loginWithGoogle,
68 | alert,
69 | setAlert,
70 | loading,
71 | setLoading,
72 | resetPassword,
73 | };
74 | return {children};
75 | };
76 |
77 | export default AuthContext;
78 |
--------------------------------------------------------------------------------
/src/firebase/addDocument.js:
--------------------------------------------------------------------------------
1 | import { collection, doc, serverTimestamp, setDoc } from 'firebase/firestore';
2 | import { db } from './config';
3 |
4 | const addDocument = (collectionName, documentObj, id) => {
5 | const docRef = doc(collection(db, collectionName), id);
6 | return setDoc(docRef, {
7 | ...documentObj,
8 | timestamp: serverTimestamp(),
9 | });
10 | };
11 |
12 | export default addDocument;
13 |
--------------------------------------------------------------------------------
/src/firebase/config.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from 'firebase/app';
3 | import { getStorage } from 'firebase/storage';
4 | import { getFirestore } from 'firebase/firestore';
5 | import { getAuth } from 'firebase/auth';
6 | // TODO: Add SDKs for Firebase products that you want to use
7 | // https://firebase.google.com/docs/web/setup#available-libraries
8 |
9 | // Your web app's Firebase configuration
10 | const firebaseConfig = {
11 | apiKey: process.env.REACT_APP_apiKey,
12 | authDomain: process.env.REACT_APP_authDomain,
13 | projectId: process.env.REACT_APP_projectId,
14 | storageBucket: process.env.REACT_APP_storageBucket,
15 | messagingSenderId: process.env.REACT_APP_messagingSenderId,
16 | appId: process.env.REACT_APP_appId,
17 | };
18 |
19 | // Initialize Firebase
20 | export const app = initializeApp(firebaseConfig);
21 | export const storage = getStorage();
22 | export const db = getFirestore();
23 | export const auth = getAuth();
24 |
--------------------------------------------------------------------------------
/src/firebase/deleteDocument.js:
--------------------------------------------------------------------------------
1 | import { deleteDoc, doc } from 'firebase/firestore';
2 | import { db } from './config';
3 |
4 | const deleteDocument = (collectionName, documentId) => {
5 | return deleteDoc(doc(db, collectionName, documentId));
6 | };
7 |
8 | export default deleteDocument;
9 |
--------------------------------------------------------------------------------
/src/firebase/deleteFile.js:
--------------------------------------------------------------------------------
1 | import { deleteObject, ref } from 'firebase/storage';
2 | import { storage } from './config';
3 |
4 | const deleteFile = (filePath) => {
5 | const imageRef = ref(storage, filePath);
6 | return deleteObject(imageRef);
7 | };
8 |
9 | export default deleteFile;
10 |
--------------------------------------------------------------------------------
/src/firebase/deleteUserFiles.js:
--------------------------------------------------------------------------------
1 | import { collection, getDocs, query, where } from 'firebase/firestore';
2 | import { db } from './config';
3 | import deleteDocument from './deleteDocument';
4 | import deleteFile from './deleteFile';
5 |
6 | const deleteUserFiles = (collectionName, currentUser) => {
7 | return new Promise(async (resolve, reject) => {
8 | const q = query(
9 | collection(db, collectionName),
10 | where('uid', '==', currentUser.uid)
11 | );
12 | try {
13 | const snapshot = await getDocs(q);
14 | const storePromises = [];
15 | const storagePromises = [];
16 | snapshot.forEach((document) => {
17 | storePromises.push(deleteDocument(collectionName, document.id));
18 | storagePromises.push(
19 | deleteFile(`${collectionName}/${currentUser.uid}/${document.id}`)
20 | );
21 | });
22 | await Promise.all(storePromises);
23 | await Promise.all(storagePromises);
24 |
25 | if (currentUser?.photoURL) {
26 | const photoName = currentUser?.photoURL
27 | ?.split(`${currentUser.uid}%2F`)[1]
28 | ?.split('?')[0];
29 | if (photoName) {
30 | try {
31 | await deleteFile(`profile/${currentUser.uid}/${photoName}`);
32 | } catch (error) {
33 | console.log(error);
34 | }
35 | }
36 | }
37 |
38 | resolve();
39 | } catch (error) {
40 | reject(error);
41 | }
42 | });
43 | };
44 |
45 | export default deleteUserFiles;
46 |
--------------------------------------------------------------------------------
/src/firebase/updateUserRecords.js:
--------------------------------------------------------------------------------
1 | import {
2 | collection,
3 | doc,
4 | getDocs,
5 | query,
6 | updateDoc,
7 | where,
8 | } from 'firebase/firestore';
9 | import { db } from './config';
10 |
11 | const updateUserRecords = (collectionName, uid, updatedObj) => {
12 | return new Promise(async (resolve, reject) => {
13 | const q = query(collection(db, collectionName), where('uid', '==', uid));
14 | try {
15 | const snapshot = await getDocs(q);
16 | const updatePromises = [];
17 | snapshot.forEach((document) => {
18 | updatePromises.push(
19 | updateDoc(doc(db, collectionName, document.id), updatedObj)
20 | );
21 | });
22 | await Promise.all(updatePromises);
23 | resolve();
24 | } catch (error) {
25 | reject(error);
26 | }
27 | });
28 | };
29 |
30 | export default updateUserRecords;
31 |
--------------------------------------------------------------------------------
/src/firebase/uploadFile.js:
--------------------------------------------------------------------------------
1 | import { getDownloadURL, ref, uploadBytes } from 'firebase/storage';
2 | import { storage } from './config';
3 |
4 | const uploadFile = (file, filePath) => {
5 | return new Promise(async (resolve, reject) => {
6 | const storageRef = ref(storage, filePath);
7 | try {
8 | await uploadBytes(storageRef, file);
9 | const url = await getDownloadURL(storageRef);
10 | resolve(url);
11 | } catch (error) {
12 | reject(error);
13 | }
14 | });
15 | };
16 |
17 | export default uploadFile;
18 |
--------------------------------------------------------------------------------
/src/firebase/uploadFileProgress.js:
--------------------------------------------------------------------------------
1 | import { getDownloadURL, ref, uploadBytesResumable } from 'firebase/storage';
2 | import { storage } from './config';
3 |
4 | const uploadFileProgress = (file, subFolder, imageName, setProgress) => {
5 | return new Promise((resolve, reject) => {
6 | const storageRef = ref(storage, subFolder + '/' + imageName);
7 | const upload = uploadBytesResumable(storageRef, file);
8 | upload.on(
9 | 'state_change',
10 | (snapshot) => {
11 | const progress =
12 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
13 | setProgress(progress);
14 | },
15 | (error) => {
16 | reject(error);
17 | },
18 | async () => {
19 | try {
20 | const url = await getDownloadURL(storageRef);
21 | resolve(url);
22 | } catch (error) {
23 | reject(error);
24 | }
25 | }
26 | );
27 | });
28 | };
29 |
30 | export default uploadFileProgress;
31 |
--------------------------------------------------------------------------------
/src/firebase/useFirestore.js:
--------------------------------------------------------------------------------
1 | import { collection, onSnapshot, orderBy, query } from 'firebase/firestore';
2 | import { useEffect, useState } from 'react';
3 | import { useAuth } from '../context/AuthContext';
4 | import { db } from './config';
5 |
6 | const useFirestore = (collectionName = 'gallery') => {
7 | const [documents, setDocuments] = useState([]);
8 | const { setAlert } = useAuth();
9 | useEffect(() => {
10 | const q = query(
11 | collection(db, collectionName),
12 | orderBy('timestamp', 'desc')
13 | );
14 | const unsubscribe = onSnapshot(
15 | q,
16 | (snapshot) => {
17 | const docs = [];
18 | snapshot.forEach((doc) => {
19 | docs.push({ id: doc.id, data: doc.data() });
20 | });
21 | setDocuments(docs);
22 | },
23 | (error) => {
24 | setAlert({
25 | isAlert: true,
26 | severity: 'error',
27 | message: error.message,
28 | timeout: 8000,
29 | location: 'main',
30 | });
31 | console.log(error);
32 | }
33 | );
34 |
35 | return () => unsubscribe();
36 | }, [collectionName]);
37 |
38 | return { documents };
39 | };
40 |
41 | export default useFirestore;
42 |
--------------------------------------------------------------------------------
/src/img/profile.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codelikepro22/react-firebase-images-gallery/d7bd34afdcc85b451bacadce6ca46876811594be/src/img/profile.jpeg
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | resolve: {
3 | fallback: { process: require.resolve('process/browser') },
4 | },
5 | };
6 |
--------------------------------------------------------------------------------