728x90
반응형

이제 본격적으로 만들어볼겁니다.
우선 홈페이지 UI제작을 할겁니다. 네브바를 만들기 위해 layout.tsx로 갑니다.
그리고 각각의 div를 만들어줄겁니다.

import { Outlet } from "react-router-dom";
import styled from "styled-components";

const Wrapper = styled.div``;

const Menu = styled.div``;

const MenuItem = styled.div``;

export default function Layout(){
    return (
        <Wrapper>
          <Menu>
            <MenuItem>
            </MenuItem>

            <MenuItem>
            </MenuItem>

            <MenuItem>
            </MenuItem>
          </Menu>
          <Outlet />
        </Wrapper>
    );
}

이렇게 나눠주고 이제 각 MenuItem에는 각각 홈버튼, 프로필 버튼, 로그아웃 버튼을 만들겁니다.
아이콘을 가져오기위해 heroicons.dev 로 접속하고 각각에 어울리는 icon을 복사 붙여넣기 할겁니다. 
이때 STRICT JSX를 선택하고 COPY 해서 오시면됩니다.

이제 각각의 아이콘에 Link를 걸어야합니다.
홈버튼은 to="/", 프로필은 to="/profile"으로 보내면 되겠네요. 로그아웃은 필요가 없습니다.

이제 각 div에 css를 적용할겁니다.

const Wrapper = styled.div`
  display: grid;
  gap: 20px;
  grid-template-columns: 1fr 4fr;
  padding: 50px 0px;
  height: 100%;
  width: 100%;
  max-width: 860px;
`;

const Menu = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
`;

const MenuItem = styled.div`
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid white;
  height: 50px;
  width: 50px;
  border-radius: 50%;
  svg {
    width: 30px;
    fill: white;
  }
  &.log-out {
    border-color: tomato;
    svg {
      color: tomato;
    }
  }
`;

logout쪽 MenuItem에는 className="log-out"을 사용해서 조금 다른 css를 적용해줍니다.

이제 로그아웃 로직을 작성할겁니다.
사용자를 로그아웃 시키고 login 화면으로 안내해야합니다.

export default function Layout() {
  const navigate = useNavigate();
  const onLogOut = async () => {
    const ok = confirm("로그아웃 하시겠습니까?");
    if (ok) {
      await auth.signOut();
      navigate("/login");
    }
  } 
-------------------------------------생략-----------------------------------
        <MenuItem onClick={onLogOut} className="log-out">

}

이동에는 useNavigate를 사용하고 async await를 사용하고 로그아웃 전에 확인절차를 하나 만들어둡니다.
로그아웃쪽 MenuItem에는 onClick 이벤트를 넣어서 적용해줍니다.

트윗페이지를 제작하기 위해서 components 밑으로 post-tweet-form.tsx를 만들어줍니다.

import styled from "styled-components"

const Form = styled.form``;

const TextArea = styled.textarea``;

const AttachFileButton = styled.label``;

const AttachFileInput = styled.input``;

const SubmitBtn = styled.input``;

export default function PostTweetFrom() {
    return (
        <Form>
            <TextArea placeholder="What is happening" />
                <AttachFileButton htmlFor="file">Add photo</AttachFileButton>
                <AttachFileInput type="file" id="file" accept="image/*" />
                <SubmitBtn />
        </Form>
    )
}

코드를 채워줍니다. 
<AttachFileInput type="file" id="file" accept="image/*" /> 부분에서는 이미지는 어떠한 확장자라도 받을 수 있게 image/* 로 작성합니다.

home.tsx로 이동해서 PostTweetForm을 가져올겁니다.

import styled from "styled-components";
import PostTweetFrom from "../components/post-tweet-form";

const Wrapper = styled.div``;

export default function Home() {
    return (
        <Wrapper>
          <PostTweetFrom />
        </Wrapper>
    );
}

그리고 확인해보면

폼이 완성되어 있습니다.
여기서 Add photo 를 클릭하면 파일 선택과 같은 동작을 하는것을 볼 수 있습니다. input 버튼을 꾸미는건 까다롭기 때문에 
display: none; 으로 지워줍니다.
submitBtn도 타입을 submit로 지정하고 value로 버튼에 나타낼 텍스트를 정해줍니다.

<SubmitBtn type="submit" value="Post Tweet" />

이제 css를 좀 만질겁니다.

const Form = styled.form`
    display: flex;
    flex-direction: column;
    gap: 10px;
`;

const TextArea = styled.textarea`
    border: 2px solid white;
    padding: 20px;
    border-radius: 20px;
    font-size: 16px;
    color: white;
    background-color: black;
    width: 100%;
    resize: none;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    &::placeholder{
        font-size: 16px;
    }
    &:focus {
        outline: none;
        border-color: #1d9bf0;
    }
`;

const AttachFileButton = styled.label`
    padding: 10px 0px;
    color: #1d9bf0;
    text-align: center;
    border-radius: 20px;
    border: 1px  solid #1d9bf0;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
`;

const AttachFileInput = styled.input`
    display: none;
`;

const SubmitBtn = styled.input`
    background-color: #1d9bf0;
    color: white;
    border: none;
    padding: 10px 0px;
    border-radius: 20px;
    font-size: 16px;
    cursor: pointer;
    &:hover,
    & active{
        opacity: 0.8;
    }
`;

뚝딱뚝딱 하면 대충 각이 잡힌 폼이 완성됩니다.

이제 사진을 추가했을때 버튼의 텍스트가 달라지게 만들겁니다.

export default function PostTweetFrom() {
    const [isLoading, setLoading] = useState(false);
    const [tweet, setTweet] = useState("");
    const [file, setFile] = useState<File | null>(null);
    const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        setTweet(e.target.value);
    }
    const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { files } = e.target;
        if (files && files.length === 1) {
            setFile(files[0]);
        }
    }
    return (
        <Form>
            <TextArea rows={5} maxLength={200} placeholder="What is happening" />
                <AttachFileButton htmlFor="file">{file ? "Photo added✅":"Add photo"}</AttachFileButton>
                <AttachFileInput onChange={onFileChange} type="file" id="file" accept="image/*" />
                <SubmitBtn type="submit" value="Post Tweet" />
        </Form>
    )
}


onChangeFile의 로직을 살펴보면 files에 배열이 있는지 확인하고 그 길이가 1이라면 첫번째 파일을 가져와서 저장해줍니다.
여러개의 input방지를 위해 이렇게 작성해줍니다.
<AttachFileButton htmlFor="file">{file ? "Photo added✅":"Add photo"}</AttachFileButton>
이부분에서 삼항연산자를 사용해서 file이 추가되었다면 추가된 표시를 아니라면 Add photo로 나둡니다.

게시글 작성도 200자 이하로 작성하도록 해줍니다.

728x90
반응형