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

## 環境の準備
①ターミナルで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](https://www.youtube.com/watch?v=_cs47zz-LnM&t=167s)