1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React (redux-toolkit, firebasev9 , react-router-domv6)の構成でアプリを作成しました【7】【google-drive-clone】

Last updated at Posted at 2022-03-12

環境の準備

①ターミナルでreactアプリケーションを作成する。

$ npx create-react-app <プロジェクト名>
% cd <プロジェクト名>
% npm start

② 必要なパッケージをインストールする。

$ npm install styled-components
$ npm install @mui/material @emotion/react @emotion/styled 
$ npm install @mui/icons-material
$ npm install firebase
$ npm install firebase-tools
$ npm install react-router-dom
$ npm install --save @reduxjs/toolkit react-redux @types/react-redux

③ 不要なファイルを削除する。

App.css, App.test.js, logo.svg, setupTest.js

コンポーネント・ファイル構成

 public
  ├── img
 src  └── logo1.png
  ├── app
       └── store.js
  ├── components
       ├── Drive.js
       ├── FileContainer.js
       ├── FileList.js
       ├── Folder.js
       ├── Header.js
       ├── Login.js
       ├── Model.js
       ├── PhotoDisplay.js
       ├── photoModel.js
       ├── SideBar.js
       └── SideBarList.js
  ├── firebase
       └── firebase.js
  ├── slices
       ├── bool
            └── boolSlice.js
    ├── channel
            └── channelSlice.js
       ├── photodisplay
            └── photoSlice.js
    └── user
          └── userSlice.js
  ├── App.js
  ├── index.css
  ├── index.js
src/app/store.js
import { configureStore } from "@reduxjs/toolkit";
import boolReducer from "../slices/Bool/boolSlice";
import photoReducer from "../slices/photodisplay/photoSlice";
import userReducer from "../slices/user/userSlice";
import folderReducer from "../slices/channel/channelSlice";

export const store = configureStore({
  reducer: {
    bool: boolReducer,
    photos: photoReducer,
    user: userReducer,
    folder: folderReducer,
  },
});
src/components/Drive.js
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import FileList from "./FileList";
import FileContainer from "./FileContainer";
import { useDispatch } from "react-redux";
import { setBoolean } from "../slices/Bool/boolSlice";
import db from "../firebase/firebase";
import { collection, onSnapshot, orderBy, query } from "firebase/firestore";
import { setFolder } from "../slices/channel/channelSlice";

function Drive() {
  const dispatch = useDispatch();
  const [folders, setFolders] = useState([]);
  const [fileData, setFileData] = useState([]);

  useEffect(() => {
    dispatch(setFolder({ folderId: null, folderName: null }));
  }, [dispatch]);

  useEffect(() => {
    return onSnapshot(
      query(collection(db, "folder"), orderBy("timestamp", "asc")),
      (snapshot) => setFolders(snapshot.docs)
    );
  }, []);

  useEffect(() => {
    return onSnapshot(
      query(collection(db, "post"), orderBy("timestamp", "asc")),
      (snapshot) => setFileData(snapshot.docs)
    );
  }, []);

  return (
    <Container onClick={() => dispatch(setBoolean({ modelBools: false }))}>
      <Title>
        <span>My Drive</span>
        <ArrowDropDownIcon />
      </Title>
      <FileContent>
        <SemiTitle>Suggested</SemiTitle>

        <GridContainer>
          {fileData?.map((data) => {
            return (
              <FileList
                img={data?.data().Image}
                key={data?.id}
                id={data?.id}
                title={data?.data().photoTitle}
                uid={data?.data().uid}
              />
            );
          })}
        </GridContainer>
        <Margin>
          <SemiTitle>Folders</SemiTitle>

          <GridContainer>
            {folders?.map((data) => (
              <FileContainer
                key={data.id}
                id={data.id}
                title={data?.data().name}
              />
            ))}
          </GridContainer>
        </Margin>
      </FileContent>
    </Container>
  );
}

export default Drive;

const Container = styled.div`
  flex-grow: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 15px 30px;
`;

const Title = styled.div`
  display: flex;
  flex-direction: row;
  align-items: center;
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
  padding-bottom: 13px;

  svg {
    margin-left: 10px;
    color: #5f6368;
  }

  span {
    font-family: Google Sans, Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
    font-weight: 400;
    font-size: 18px;
    color: #202124;
  }
`;

const FileContent = styled.div`
  display: flex;
  flex-direction: column;
  padding-top: 20px;
  overflow-y: scroll;
  flex-grow: 1;
  max-height: 100vh;
  margin-bottom: 30px;

  ::-webkit-scrollbar {
    width: 15px;
  }

  ::-webkit-scrollbar-track {
    background-color: rgba(0, 0, 0, 0.2);
    border-radius: 20px;
  }

  ::-webkit-scrollbar-thumb {
    background-color: rgba(0, 0, 0, 0.2);
    border-radius: 20px;
    transition: all 200ms ease-out;
    max-height: 100px;

    :hover {
      background-color: rgba(0, 0, 0, 0.3);
    }
  }
`;

const SemiTitle = styled.div`
  font-size: 14px;
  font-weight: 500;
  text-transform: capitalize;
  color: #5f6368;
`;

const GridContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 20px 0;
`;

const Margin = styled.div``;
src/components/FileContainer.js
import { Folder } from "@mui/icons-material";
import React from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { setFolder } from "../slices/channel/channelSlice";

function FileContainer({ title, id }) {
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const SelectChannel = () => {
    if (id) {
      dispatch(setFolder({ folderId: id, folderName: title }));
      navigate(`/folder/${title}/${id}`);
    }
  };
  return (
    <Container onClick={SelectChannel}>
      <Folder />
      <span>{title}</span>
    </Container>
  );
}

export default FileContainer;

const Container = styled.div`
  width: 287.5px;
  height: 48px;
  display: flex;
  align-items: center;
  border: 1px solid rgba(0, 0, 0, 0.35);
  border-radius: 4px;

  svg {
    height: 24px;
    width: 24px;
    color: rgba(95, 99, 104);
    margin-left: 4px;
  }

  span {
    font-size: 13px;
    margin-left: 10px;
    text-transform: capitalize;
  }
`;
src/components/FileList.js
import { InsertPhoto } from "@mui/icons-material";
import { useDispatch } from "react-redux";
import styled from "styled-components";
import { setPhotoDisplay } from "../slices/photodisplay/photoSlice";

const FileList = ({ img, title }) => {
  const dispatch = useDispatch();

  const PhotoSelector = () => {
    dispatch(setPhotoDisplay({ photo: img, title: title }));
  };
  return (
    <Container>
      <PhotoContainer onClick={PhotoSelector}>
        <img src={img} alt="" />
      </PhotoContainer>
      <PhotoTitle>
        <InsertPhoto />
        <span>{title}</span>
      </PhotoTitle>
    </Container>
  );
};

export default FileList;

const Container = styled.div`
  max-width: 300px;
  max-height: 400px;
  height: 209px;
  display: flex;
  flex-direction: column;
  border-radius: 20px;
  margin: 10px 0;
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 /0.1);
`;

const PhotoContainer = styled.div`
  height: 60%;
  width: 100%;
  background-color: lightgray;
  border-top-left-radius: inherit;
  border-top-right-radius: inherit;
  img {
    height: 100%;
    width: 100%;
    border-top-left-radius: inherit;
    border-top-right-radius: inherit;
    object-fit: contain;
    border-bottom: 1px solid rgba(0, 0, 0, 0.2);
  }
`;
const PhotoTitle = styled.div`
  display: flex;
  align-items: center;
  margin-top: 20px;
  margin-left: 10px;
  svg {
    color: #70b5f9;
  }
  span {
    color: rgba(0, 0, 0, 0.72);
    margin-left: 28px;
    padding-bottom: 4px;
    font-size: 13px;
    font-weight: 600;
  }
`;
src/components/Folder.js
import { collection, onSnapshot, orderBy, query } from "firebase/firestore";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import db from "../firebase/firebase";
import { setBoolean } from "../slices/Bool/boolSlice";
import { selectFolderId } from "../slices/channel/channelSlice";
import FileList from "./FileList";

function Folder() {
  const dispatch = useDispatch();
  const folderId = useSelector(selectFolderId);
  const [filePhoto, setFilePhoto] = useState([]);
  const navigate = useNavigate();

  useEffect(() => {
    if (!folderId) {
      navigate("/");
      document.location.reload();
    }
  }, [folderId, navigate]);

  useEffect(() => {
    return onSnapshot(
      query(
        collection(db, "folder", folderId, "folderTree"),
        orderBy("timestamp", "asc")
      ),
      (snapshot) => {
        setFilePhoto(snapshot.docs);
      }
    );
  }, [folderId]);
  return (
    <Container onClick={() => dispatch(setBoolean({ modelBools: false }))}>
      <GridContainer>
        {filePhoto?.map((data) => (
          <FileList
            key={data?.id}
            img={data?.data().Image}
            title={data?.data().photoTitle}
          />
        ))}
      </GridContainer>
    </Container>
  );
}

export default Folder;

const Container = styled.div`
  flex-grow: 1;
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 15px 30px;
`;

const GridContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 20px 0;
`;
src/components/FolderModel.js
import { addDoc, collection, serverTimestamp } from "firebase/firestore";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import db from "../firebase/firebase";
import { selectFolderBool, setBoolean } from "../slices/Bool/boolSlice";
import { selectUid } from "../slices/user/userSlice";

function FolderModel() {
  const folderBool = useSelector(selectFolderBool);
  const dispatch = useDispatch();
  const uid = useSelector(selectUid);
  const [folderNames, setFolderNames] = useState("");
  const [loading, setLoading] = useState(false);

  const Submit = async (e) => {
    e.preventDefault();
    if (loading) return;
    setLoading(true);

    if (folderNames.length < 1) return;
    await addDoc(collection(db, "folder"), {
      name: folderNames,
      timestamp: serverTimestamp(),
      uid: uid,
    });

    setLoading(false);
    dispatch(setBoolean({ folderBool: false }));
    setFolderNames("");
  };
  return (
    <Container folder={folderBool}>
      <Wrapper onSubmit={Submit}>
        <Title>New Folder</Title>
        <InputContainer>
          <input
            type="text"
            value={folderNames}
            onChange={(e) => setFolderNames(e.target.value)}
            placeholder="create Folder"
          />
        </InputContainer>

        <Button>
          <button onClick={() => dispatch(setBoolean({ folderBool: false }))}>
            Cancel
          </button>
          <button className="create" disabled={loading} onClick={Submit}>
            {loading ? "Creating" : "CreateFolder"}
          </button>
        </Button>
      </Wrapper>
    </Container>
  );
}

export default FolderModel;

const Container = styled.div`
  z-index: 9999;
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 200ms ease-out;
  transform: ${(props) =>
    props.folder ? "translateY(0)" : "translateY(100%)"};
`;

const Wrapper = styled.div`
  width: 500px;
  height: 130px;
  background-color: white;
  border-radius: 20px;
  position: relative;
`;

const Title = styled.span`
  font-size: 20px;
  margin: 20px;
`;

const InputContainer = styled.div`
  display: flex;
  flex-direction: row;
  flex: 1;
  margin: 20px;
  input {
    border: none;
    flex: 1;
    :focus {
      outline: none;
    }
  }
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
`;

const Button = styled.div`
  position: absolute;
  bottom: 5px;
  right: 10px;
  display: flex;
  align-items: center;

  button {
    padding: 10px 20px;
    border-radius: 4px;
    font-weight: 500;
    background-color: #ef4444;
    border: none;
    color: white;
    margin: 0 15px;
    cursor: pointer;
  }

  .create {
    background-color: #3b82f6;
  }
`;
src/components/Header.js
import { Avatar, ButtonGroup } from "@mui/material";
import styled from "styled-components";
import SearchIcon from "@mui/icons-material/Search";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
import AppsOutlinedIcon from "@mui/icons-material/AppsOutlined";
import { useSelector } from "react-redux";
import { selectPhoto } from "../slices/user/userSlice";
import { Link } from "react-router-dom";

function Header() {
  const photo = useSelector(selectPhoto);

  return (
    <Container>
      <Wrapper>
        <Link to="/">
          <Logo>
            <img src="/img/logo1.png" alt="" />
            <span>Drive</span>
          </Logo>
        </Link>
        <InputContainer>
          <SearchContainer>
            <ButtonGroup>
              <SearchIcon />
            </ButtonGroup>
            <input type="text" placeholder="Search in Drive" />
          </SearchContainer>
        </InputContainer>

        <RightContainer>
          <LeftSection>
            <HelpOutlineIcon />
            <SettingsOutlinedIcon />
          </LeftSection>
          <RightSection>
            <AppsOutlinedIcon className="app" />
            <Avatar src={photo} />
          </RightSection>
        </RightContainer>
      </Wrapper>
    </Container>
  );
}

export default Header;

const Container = styled.div`
  position: sticky;
  top: 0;
  z-index: 999;
  background-color: #ffffff;
  padding: 2px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
`;

const Wrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 10px 20px;
`;

const Logo = styled.div`
  display: flex;
  align-items: center;

  img {
    width: 40px;
    height: 40px;
  }

  span {
    font-family: "Product Sans", Arial, sans-serif;
    color: #5f6368;
    font-size: 22px;
    padding-left: 8px;
  }
`;

const InputContainer = styled.div`
  flex: 1;
`;

const RightContainer = styled.div`
  display: flex;
  align-items: center;
`;

const SearchContainer = styled.div`
  width: 64%;
  height: 50px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  background-color: rgba(0, 0, 0, 0.09);
  border-radius: 8px;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 /0.05);

  svg {
    margin-left: 10px;
    color: #5f6368;
  }

  input {
    font-size: 16px;
    width: 90%;
    height: 80%;
    font-family: Sans, Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
    margin: 0 auto;
    background-color: transparent;
    :focus {
      outline: none;
    }

    border: none;
  }
`;

const RightSection = styled.div`
  display: flex;
  align-items: center;

  svg {
    color: #5f6368;
    padding: 5px;
    cursor: pointer;
    border-radius: 50%;
    transition: all 200ms ease-out;
    :hover {
      background-color: rgba(0, 0, 0, 0.09);
    }
  }

  .app {
    margin-right: 15px;
  }
`;

const LeftSection = styled(RightSection)`
  margin-right: 40px;

  svg {
    margin: 0 10px;
  }
`;
src/components/Login.js
import { signInWithPopup } from "firebase/auth";
import { useDispatch } from "react-redux";
import styled from "styled-components";
import { auth, provider } from "../firebase/firebase";
import { setLogIn } from "../slices/user/userSlice";

function Login() {
  const dispatch = useDispatch();

  const LoginwithGoogle = async () => {
    await signInWithPopup(auth, provider).then((res) => {
      let user = res.user;

      dispatch(setLogIn({ uid: user.uid, photo: user.photoURL }));
    });
  };

  return (
    <Container>
      <Button onClick={LoginwithGoogle}>Sign In with Google</Button>
    </Container>
  );
}

export default Login;

const Container = styled.div`
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
`;

const Button = styled.button`
  font-weight: 600;
  padding: 15px 20px;
  border: none;
  cursor: pointer;
  border-radius: 15px;
  transition: all 200ms ease-out;
  :hover {
    transform: scale(1.09);
  }
`;
src/components/Model.js
import { AddPhotoAlternate, CreateNewFolder } from "@mui/icons-material";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import { selectModelBool, setBoolean } from "../slices/Bool/boolSlice";

function Model() {
  const selectModel = useSelector(selectModelBool);
  const dispatch = useDispatch();
  return (
    <Container bool={selectModel}>
      <Wrapper>
        <Header>
          <Wraps onClick={() => dispatch(setBoolean({ folderBool: true }))}>
            <CreateNewFolder />
            <span>Folder</span>
          </Wraps>
        </Header>
        <Header>
          <Wraps onClick={() => dispatch(setBoolean({ photo: true }))}>
            <AddPhotoAlternate />
            <span>Photo</span>
          </Wraps>
        </Header>
      </Wrapper>
    </Container>
  );
}

export default Model;

const Container = styled.div`
  position: fixed;
  top: 100px;
  left: 20px;
  background-color: white;
  height: 200px;
  width: 300px;
  border-radius: 20px;
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 /0.1);
  transition: all 200ms ease-out;
  transform: ${(props) =>
    props.bool ? `translateX(-0%)` : `translateX(-200%)`};
`;

const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
`;

const Header = styled.div`
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
  margin: 5px 0;
`;

const Wraps = styled.div`
  display: flex;
  align-items: center;
  padding: 5px 0;
  cursor: pointer;
  padding-left: 20px;
  margin-top: 10px;
  transition: all 200ms ease-out;
  &:hover {
    background-color: rgba(0, 0, 0, 0.2);
  }
  svg {
    color: rgba(0, 0, 0, 0.5);
  }

  span {
    margin-left: 10px;
  }
`;
src/components/PhotoDisplay.js
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import {
  selectPhotoDisplay,
  selectTitleDisplay,
  setPhotoDisplay,
} from "../slices/photodisplay/photoSlice";

function PhotoDisplay() {
  const img = useSelector(selectPhotoDisplay);
  const title = useSelector(selectTitleDisplay);
  const dispatch = useDispatch();

  return (
    <Container
      show={img}
      onClick={() => dispatch(setPhotoDisplay({ photo: null }))}
    >
      <span>{title}</span>
      <PhotoDisplays>
        <img src={img} alt="" />
      </PhotoDisplays>
    </Container>
  );
}

export default PhotoDisplay;

const Container = styled.div`
  z-index: 9999;
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  transform: ${(props) => (props.show ? "translateY(0)" : "translateY(100%)")};

  span {
    font-size: 40px;
    color: white;
    position: absolute;
    top: 0;
  }
`;

const PhotoDisplays = styled.div`
  max-width: 760px;
  max-height: 475px;
  height: 475px;
  width: 760px;
  img {
    width: 100%;
    height: 100%;
  }
`;

src/components/PhotoModel.js
import { CameraAlt, Close } from "@mui/icons-material";
import {
  addDoc,
  collection,
  doc,
  serverTimestamp,
  updateDoc,
} from "firebase/firestore";
import { getDownloadURL, ref, uploadString } from "firebase/storage";
import React, { useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import db, { storage } from "../firebase/firebase";
import { selectPhotoBool, setBoolean } from "../slices/Bool/boolSlice";
import { selectFolderId } from "../slices/channel/channelSlice";
import { selectUid } from "../slices/user/userSlice";

function PhotoModel() {
  const [input, setInput] = useState("");
  const [selectedImage, setSelectedImage] = useState(null);
  const ImageRef = useRef(null);
  const Photo = useSelector(selectPhotoBool);
  const [loading, setLoading] = useState(false);
  const uid = useSelector(selectUid);
  const folderId = useSelector(selectFolderId);

  const dispatch = useDispatch();

  const Submit = async (e) => {
    e.preventDefault();

    if (loading) return;
    setLoading(true);

    if (input.length < 1) return;

    if (folderId) {
      const doces = await addDoc(
        collection(db, "folder", folderId, "folderTree"),
        {
          uid: uid,
          photoTitle: input,
          timestamp: serverTimestamp(),
        }
      );

      const images = ref(storage, `folder/${folderId}/${doces.id}image`);

      await uploadString(images, selectedImage, "data_url").then(
        async (snapshot) => {
          const downloadUrl = await getDownloadURL(images);

          await updateDoc(doc(db, "folder", folderId, "folderTree", doces.id), {
            Image: downloadUrl,
          });
        }
      );
    } else {
      const docs = await addDoc(collection(db, "post"), {
        uid: uid,
        photoTitle: input,
        timestamp: serverTimestamp(),
      });

      const images = ref(storage, `post/${docs.id}/image`);

      await uploadString(images, selectedImage, "data_url").then(
        async (snapshot) => {
          const downloadUrl = await getDownloadURL(images);

          await updateDoc(doc(db, "post", docs.id), {
            Image: downloadUrl,
          });
        }
      );
    }

    setSelectedImage(null);
    setInput("");
    setLoading(false);
    dispatch(setBoolean({ photo: false }));
  };

  const SelectImages = (e) => {
    const Reader = new FileReader();

    if (e.target.files[0]) {
      Reader.readAsDataURL(e.target.files[0]);

      Reader.onload = (Event) => {
        setSelectedImage(Event.target.result);
      };
    }
  };

  return (
    <Container show={Photo}>
      <CloseIcon>
        <Close onClick={() => dispatch(setBoolean({ photo: false }))} />
      </CloseIcon>
      <Wrapper onSubmit={Submit}>
        <ImageContainer>
          {selectedImage ? (
            <img
              src={selectedImage}
              alt=""
              onClick={() => setSelectedImage(null)}
            />
          ) : (
            <CameraContainer>
              <CameraAlt onClick={() => ImageRef.current.click()} />
            </CameraContainer>
          )}
          <input type="file" hidden ref={ImageRef} onChange={SelectImages} />
        </ImageContainer>
        <TextContainer>
          <input
            type="text"
            placeholder="Enter photo Title"
            value={input}
            onChange={(e) => setInput(e.target.value)}
          />
        </TextContainer>
        <ButtonContainer>
          <button onClick={Submit} disabled={loading}>
            {loading ? "Submitting..." : "Submit"}
          </button>
        </ButtonContainer>
      </Wrapper>
    </Container>
  );
}

export default PhotoModel;

const Container = styled.div`
  z-index: 9999;
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  transform: ${(props) => (props.show ? "translateY(0)" : "translateY(100%)")};
`;

const Wrapper = styled.form`
  height: 400px;
  width: 400px;
  background-color: white;
  border-radius: 20px;
  position: relative;
  z-index: 999;
`;

const ImageContainer = styled.div`
  height: 50%;
  margin-bottom: 20px;
  width: 100%;
  img {
    width: 100%;
    height: 100%;
    border-top-left-radius: 20px;
    border-top-right-radius: 20px;
  }
`;

const TextContainer = styled.div`
  flex: 1;
  border-bottom: 1px solid black;
  margin: 0 20px;
  margin-top: 27px;
  input {
    display: flex;
    border: none;
    font-size: 18px;
    text-transform: capitalize;
    width: 100%;
    border: none;
    :focus {
      outline: none;
    }
  }
`;

const ButtonContainer = styled.div`
  position: absolute;
  bottom: 30px;
  right: 30px;
  button {
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    border: none;
    box-shadow: 0 1px 2px 0 rgb(0 0 0 /0.05);
    background-color: #3b82f6;
    color: white;
    transition: all 200ms ease-out;

    :hover {
      transform: scale(1.001);
    }

    :active {
      transform: scale(1.009);
    }
  }
`;

const CameraContainer = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;

  .MuiSvgIcon-root {
    width: 2.5rem !important;
    height: 2.5rem;
    color: rgba(0, 0, 0, 0.5);
    cursor: pointer;
  }
`;

const CloseIcon = styled.div`
  position: absolute;
  top: 10px;
  right: 10px;

  svg {
    cursor: pointer;
    width: 2rem;
    height: 2rem;
    color: white;
  }
`;
src/components/SideBar.js
import styled from "styled-components";
import SideBarList from "./SideBarList";
import TabletAndroidOutlinedIcon from "@mui/icons-material/TabletAndroidOutlined";
import ComputerOutlinedIcon from "@mui/icons-material/ComputerOutlined";
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
import AccessTimeOutlinedIcon from "@mui/icons-material/AccessTimeOutlined";
import StarBorderOutlinedIcon from "@mui/icons-material/StarBorderOutlined";
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
import CloudOutlinedIcon from "@mui/icons-material/CloudOutlined";
import { useDispatch } from "react-redux";
import { setBoolean } from "../slices/Bool/boolSlice";

const SideBar = () => {
  const dispatch = useDispatch();
  return (
    <Container>
      <Wrapper>
        <NewChannel onClick={() => dispatch(setBoolean({ modelBools: true }))}>
          <span>Add new</span>
        </NewChannel>
        <div>
          <SideBarList Icon={<TabletAndroidOutlinedIcon />} title="My Drive" />
          <SideBarList Icon={<ComputerOutlinedIcon />} title="computer" />
          <SideBarList Icon={<PeopleAltOutlinedIcon />} title="share" />
          <SideBarList
            Icon={<AccessTimeOutlinedIcon />}
            title="Recently used items"
          />
          <SideBarList Icon={<StarBorderOutlinedIcon />} title="With a star" />
          <SideBarList Icon={<DeleteOutlinedIcon />} title="Bin" />
          <UnderLine />
          <SideBarList Icon={<CloudOutlinedIcon />} title="Storage" />
        </div>
      </Wrapper>
    </Container>
  );
};

export default SideBar;

const Container = styled.div`
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 10px;
`;
const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
`;
const NewChannel = styled.div`
  width: 118px;
  height: 48px;
  background-color: white;
  border-radius: 24px;
  padding: 2px;
  cursor: pointer;
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 01);
  transition: all 200ms ease-out;

  &:hover {
    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  }

  span {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100%;

    &::before {
      content: ;
      height: 36px;
      padding-right: 10px;
    }
  }
`;
const UnderLine = styled.div`
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
`;
src/components/SideBarList.js
import styled from "styled-components";

const SideBarList = ({ title, Icon }) => {
  return (
    <Container>
      {Icon}
      <span>{title}</span>
    </Container>
  );
};

export default SideBarList;

const Container = styled.div`
  display: flex;
  align-items: center;
  font-size: 13px;
  font-weight: 500;
  margin: 20px 0;
  padding: 5px 10px;
  border-radius: 20px;
  cursor: pointer;
  transition: all 200ms ease-out;
  :hover {
    background-color: rgba(0, 0, 0, 0.07);
  }
  svg {
    margin-left: 10px;
    fill: #5f6368;
    height: 24px;
  }
  span {
    margin-left: 15px;
    height: 24px;
    line-height: 26px;
    text-transform: capitalize;
    color: #5f6368;
  }
`;
src/firebase/firebase.js
import { getApp, getApps, initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth, GoogleAuthProvider } from "firebase/auth";
import { getStorage } from "firebase/storage";

const firebaseConfig = {
  apiKey: " ",
  authDomain: " ",
  projectId: " ",
  storageBucket: " ",
  messagingSenderId: " ",
  appId: " ",
};

!getApps().length ? initializeApp(firebaseConfig) : getApp();
const db = getFirestore();
const auth = getAuth();
const storage = getStorage();
const provider = new GoogleAuthProvider();

export default db;

export { auth, storage, provider };
src/slices/Bool/boolSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  folderBool: false,
  modeleBool: false,
  photo: false,
};

const booleanSlice = createSlice({
  name: "bool",
  initialState,
  reducers: {
    setBoolean: (state, action) => {
      state.folderBool = action.payload.folderBool;
      state.modelBools = action.payload.modelBools;
      state.photo = action.payload.photo;
    },
  },
});

export const { setBoolean } = booleanSlice.actions;

export const selectFolderBool = (state) => state.bool.folderBool;
export const selectModelBool = (state) => state.bool.modelBools;
export const selectPhotoBool = (state) => state.bool.photo;

export default booleanSlice.reducer;
src/slices/channel/channelSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  folderId: null,
  folderName: null,
};

const folderSlice = createSlice({
  name: "folder",
  initialState,
  reducers: {
    setFolder: (state, action) => {
      state.folderId = action.payload.folderId;
      state.folderName = action.payload.folderName;
    },
  },
});

export const { setFolder } = folderSlice.actions;

export const selectFolderId = (state) => state.folder.folderId;
export const selectFolderName = (state) => state.folder.folderName;

export default folderSlice.reducer;
src/slices/photodisplay/photoSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  photo: null,
  title: null,
};

const photoSlice = createSlice({
  name: "photo",
  initialState,
  reducers: {
    setPhotoDisplay: (state, action) => {
      state.title = action.payload.title;
      state.photo = action.payload.photo;
    },
  },
});

export const { setPhotoDisplay } = photoSlice.actions;

export const selectPhotoDisplay = (state) => state.photos.photo;
export const selectTitleDisplay = (state) => state.photos.title;

export default photoSlice.reducer;
src/slices/user/userSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  uid: null,
  photo: null,
};

const UserSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    SetLogIn: (state, action) => {
      state.uid = action.payload.uid;
      state.photo = action.payload.photo;
    },
    setLogOut: (state) => {
      state.uid = null;
      state.photo = null;
    },
  },
});

export const { setLogIn, setLogOut } = UserSlice.actions;

export const selectUid = (state) => state.user.uid;
export const selectPhoto = (state) => state.user.photo;

export default UserSlice.reducer;
App.js
import React, { useEffect } from "react";
import styled from "styled-components";
import Header from "./components/Header";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import SideBar from "./components/SideBar";
import Drive from "./components/Drive";
import Model from "./components/Model";
import FolderModel from "./components/FolderModel";
import Folder from "./components/Folder";
import PhotoModel from "./components/PhotoModel";
import PhotoDisplay from "./components/PhotoDisplay";
import { useSelector, useDispatch } from "react-redux";
import { selectUid, setLogIn, setLogOut } from "./slices/user/userSlice";
import Login from "./components/Login";
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "./firebase/firebase";

function App() {
  const user = useSelector(selectUid);
  const dispatch = useDispatch();

  useEffect(() => {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        dispatch(setLogIn({ uid: user.uid, photo: user.photoURL }));
      } else {
        dispatch(setLogOut({ uid: null, photo: null }));
      }
    });
  });

  return (
    <Router>
      <Header />
      {user ? (
        <>
          <Container>
            <SideBar />
            <Routes>
              <Route path="/" element={<Drive />} />
              <Route path="/folder/:name/:id" element={<Folder />} />
            </Routes>
          </Container>
          <Model />
          <PhotoModel />
          <FolderModel />
          <PhotoDisplay />
        </>
      ) : (
        <Login />
      )}
    </Router>
  );
}

export default App;

const Container = styled.div`
  display: flex;
`;
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { store } from "./app/store";
import { Provider } from "react-redux";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

参考サイト

Part 1 - Full Stack Social Video Blog - Reactjs | Firebase - For Beginners

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?