React.JS Firestore Project to Build Instagram Grid Image Gallery Using Cloud Storage & Framer Motion in Javascript

You are currently viewing React.JS Firestore Project to Build Instagram Grid Image Gallery Using Cloud Storage & Framer Motion in Javascript

React.js Firestore Project to Build Instagram Grid Image Gallery Using Cloud Storage & Framer Motion in Javascript

import React, { useState } from "react";
import Title from "./comps/Title";
import UploadForm from "./comps/UploadForm";
import ImageGrid from "./comps/ImageGrid";
import Modal from "./comps/Modal";
 
function App() {
  const [selectedImg, setSelectedImg] = useState(null);
 
  return (
    <div className="App">
      <Title />
      <UploadForm />
      <ImageGrid setSelectedImg={setSelectedImg} />
      {selectedImg && (
        <Modal selectedImg={selectedImg} setSelectedImg={setSelectedImg} />
      )}
    </div>
  );
}
 
export default App;

@import url('https://fonts.googleapis.com/css2?family=Noto+Serif:wght@400;700&display=swap');
 
:root{
  --primary: #efb6b2;
  --secondary: #4e4e4e;
  --error: #ff4a4a;
}
 
/* base styles & title */
body{
  font-family: "Noto Serif";
  color: var(--secondary);
}
.App{
  max-width: 960px;
  margin: 0 auto;
}
.title h1{
  color: var(--primary);
  font-size: 1.2rem;
  letter-spacing: 2px;
  font-weight: normal;
}
.title h2, .title p{
  text-align: center;
}
.title h2{
  margin-top: 60px;
  font-size: 2.6rem;
}
 
/* upload form styles */
form{
  margin: 30px auto 10px;
  text-align: center;
}
label input{
  height: 0;
  width: 0;
  opacity: 0;
}
label{
  display: block;
  width: 30px;
  height: 30px;
  border: 1px solid var(--primary);
  border-radius: 50%;
  margin: 10px auto;
  line-height: 30px;
  color: var(--primary);
  font-weight: bold;
  font-size: 24px;
}
label:hover{
  background: var(--primary);
  color: white;
}
.output{
  height: 60px;
  font-size: 0.8rem;
}
.error{
  color: var(--error);
}
 
/* progress bar styles */
.progress-bar{
  height: 5px;
  background: var(--primary);
  margin-top: 20px;
}
 
/* image grid styles */
.img-grid{
  margin: 20px auto;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 40px;
}
.img-wrap{
  overflow: hidden;
  height: 0;
  padding: 50% 0;
  /* padding controls height, will always be perfectly square regardless of width */
  position: relative;
  opacity: 0.8;
}
.img-wrap img{
  min-width: 100%;
  min-height: 100%;
  max-width: 150%;
  position: absolute;
  top: 0;
  left: 0;
}
 
/* modal styles */
.backdrop{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.5);
}
.backdrop img{
  display: block;
  max-width: 60%;
  max-height: 80%;
  margin: 60px auto;
  box-shadow: 3px 5px 7px rgba(0,0,0,0.5);
  border: 3px solid white;
}

.App {
  text-align: center;
}
 
.App-logo {
  height: 40vmin;
  pointer-events: none;
}
 
@media (prefers-reduced-motion: no-preference) {
  .App-logo {
    animation: App-logo-spin infinite 20s linear;
  }
}
 
.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}
 
.App-link {
  color: #61dafb;
}
 
@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

import firebase from "firebase/compat/app";
import "firebase/compat/storage";
import "firebase/compat/firestore";
 
const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
};
 
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
 
const projectStorage = firebase.storage();
const projectFirestore = firebase.firestore();
const timestamp = firebase.firestore.FieldValue.serverTimestamp;
 
export { projectStorage, projectFirestore, timestamp };

import { useState, useEffect } from "react";
import { projectFirestore } from "../firebase/config";
 
const useFirestore = (collection) => {
  const [docs, setDocs] = useState([]);
 
  useEffect(() => {
    const unsub = projectFirestore
      .collection(collection)
      .orderBy("createdAt", "desc")
      .onSnapshot((snap) => {
        let documents = [];
        snap.forEach((doc) => {
          documents.push({ ...doc.data(), id: doc.id });
        });
        setDocs(documents);
      });
 
    return () => unsub();
    // this is a cleanup function that react will run when
    // a component using the hook unmounts
  }, [collection]);
 
  return { docs };
};
 
export default useFirestore;

import { useState, useEffect } from "react";
import {
  projectStorage,
  projectFirestore,
  timestamp,
} from "../firebase/config";
 
const useStorage = (file) => {
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState(null);
  const [url, setUrl] = useState(null);
 
  useEffect(() => {
    // references
    const storageRef = projectStorage.ref(file.name);
    const collectionRef = projectFirestore.collection("images");
 
    storageRef.put(file).on(
      "state_changed",
      (snap) => {
        let percentage = (snap.bytesTransferred / snap.totalBytes) * 100;
        setProgress(percentage);
      },
      (err) => {
        setError(err);
      },
      async () => {
        const url = await storageRef.getDownloadURL();
        const createdAt = timestamp();
        await collectionRef.add({ url, createdAt });
        setUrl(url);
      }
    );
  }, [file]);
 
  return { progress, url, error };
};
 
export default useStorage;

import React from "react";
import useFirestore from "../hooks/useFirestore";
import { motion } from "framer-motion";
 
const ImageGrid = ({ setSelectedImg }) => {
  const { docs } = useFirestore("images");
 
  return (
    <div className="img-grid">
      {docs &&
        docs.map((doc) => (
          <motion.div
            className="img-wrap"
            key={doc.id}
            layout
            whileHover={{ opacity: 1 }}
            s
            onClick={() => setSelectedImg(doc.url)}
          >
            <motion.img
              src={doc.url}
              alt="uploaded pic"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              transition={{ delay: 1 }}
            />
          </motion.div>
        ))}
    </div>
  );
};
 
export default ImageGrid;

import React from "react";
import useFirestore from "../hooks/useFirestore";
import { motion } from "framer-motion";
 
const ImageGrid = ({ setSelectedImg }) => {
  const { docs } = useFirestore("images");
 
  return (
    <div className="img-grid">
      {docs &&
        docs.map((doc) => (
          <motion.div
            className="img-wrap"
            key={doc.id}
            layout
            whileHover={{ opacity: 1 }}
            s
            onClick={() => setSelectedImg(doc.url)}
          >
            <motion.img
              src={doc.url}
              alt="uploaded pic"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              transition={{ delay: 1 }}
            />
          </motion.div>
        ))}
    </div>
  );
};
 
export default ImageGrid;

import React, { useEffect } from "react";
import useStorage from "../hooks/useStorage";
import { motion } from "framer-motion";
 
const ProgressBar = ({ file, setFile }) => {
  const { progress, url } = useStorage(file);
 
  useEffect(() => {
    if (url) {
      setFile(null);
    }
  }, [url, setFile]);
 
  return (
    <motion.div
      className="progress-bar"
      initial={{ width: 0 }}
      animate={{ width: progress + "%" }}
    ></motion.div>
  );
};
 
export default ProgressBar;

import React from "react";
 
const Title = () => {
  return (
    <div className="title">
      <h1>FireGram</h1>
      <h2>Your Pictures</h2>
    </div>
  );
};
 
export default Title;

import React, { useState } from "react";
import ProgressBar from "./ProgressBar";
 
const UploadForm = () => {
  const [file, setFile] = useState(null);
  const [error, setError] = useState(null);
 
  const types = ["image/png", "image/jpeg"];
 
  const handleChange = (e) => {
    let selected = e.target.files[0];
 
    if (selected && types.includes(selected.type)) {
      setFile(selected);
      setError("");
    } else {
      setFile(null);
      setError("Please select an image file (png or jpg)");
    }
  };
 
  return (
    <form>
      <label>
        <input type="file" onChange={handleChange} />
        <span>+</span>
      </label>
      <div className="output">
        {error && <div className="error">{error}</div>}
        {file && <div>{file.name}</div>}
        {file && <ProgressBar file={file} setFile={setFile} />}
      </div>
    </form>
  );
};
 
export default UploadForm;

Ranjith

Hi, I'm Manoj a full-time Blogger, YouTuber, Affiliate Marketer, & founder of Coding Diksha. Here, I post about programming to help developers.

Leave a Reply