김재욱의 이모저모

Netflix 클론코딩 기록 남기기 (2) - 로그인 페이지 본문

Front-end

Netflix 클론코딩 기록 남기기 (2) - 로그인 페이지

kjyook 2023. 10. 20. 22:15
728x90

 넷플릭스 클론코딩의 첫 단계로 로그인 페이지를 구현하였다. 이번 클론코딩을 처음으로 진행하면서 알 수 있었던 사실은 HTML에도 많은 기능이 있고, 그걸 모르면서 코딩을 한 나는 정말 아무것도 모르고 코드를 쓰고 있었음을 알 수 있었다... ㅠ_ㅠ 내가 이번에 클론코딩을 진행하면서 새롭게 알고, 배운 사실들을 여기에 기록하면서 한 단계씩 진행해볼까 한다.

 

useState 를 통한 상태관리

로그인 화면

회원가입 화면

 위의 두 화면은 하단의 흰색 글씨를 누르면 서로의 화면으로 바뀌어야 했다. 물론 서로 다른 화면으로 분리해서 만든 후 링크를 걸어도 됐지만 디자인도 같은데 굳이 넘어다니는데 사용자가 더 긴 시간을 허비하게 할 필요가 없다고 생각하였다. (만약 보안상의 다른 문제나 따로 고려해야 할 것이 있다면 그때 분리해보자...)

 

 위 두 화면의 차이점으로는 회원가입 화면으로 바뀔때는 input 박스가 하나 더 생겨야 하고 회원가입일때와 로그인 일때 각각의 텍스트들이 달라야 하는 부분들이 있었다. 한 컴포넌트가 클릭되었을때 그 컴포넌트의 엘리먼트를 바꾸는 것은 그냥 onclick 한 줄이면 너무 쉽게 끝나지만, 여기서는 화면 전체가 동시에 바뀌어야 했기에 useState를 썼다.

 

import { Input } from "@/components/Input";
import { useState, useCallback } from "react";

const Login = () => {
    const [email, setEmail] = useState<string>('');
    const [password, setPassword] = useState<string>('');
    const [username, setUsername] = useState<string>('');

    const [variant, setVariant] = useState('login');
    const toggleVariant = useCallback(() => {
        setVariant((prev) => prev === 'login' ? 'signup' : 'login');
    }, [])

    return (
        <div className="relative h-full w-full bg-[url('/images/hero.jpg')] bg-no-repeat bg-center bg-fixed bg-cover">
            <div className="bg-black w-full h-full bg-opacity-50">
                <nav className="px-12 py-5">
                    <img src="/images/logo.png" alt="logo" className="h-12" />
                </nav>
                <div className="flex justify-center">
                    <div className="bg-black bg-opacity-70 px-16 py-16 self-center my-2 lg:max-w-md rounded-md w-full">
                        <h2 className="text-white text-4xl mb-8 font-semibold">
                            {variant === 'login' ? '로그인' : '회원가입'}
                        </h2>
                        <div className="flex flex-col gap-4">
                            {variant === 'signup' && (
                                <Input id="username" description="이름" secret="text" value={username} onChange={(ev) => setUsername(ev.target.value)} />
                            )}
                            <Input id="email" description="이메일 주소 또는 전화번호" secret="email" value={email} onChange={(ev) => setEmail(ev.target.value)} />
                            <Input id="password" description="비밀번호" secret="password" value={password} onChange={(ev) => setPassword(ev.target.value)}/>
                        </div>
                        <button className="bg-red-600 py-3 text-white rounded-md w-full mt-10">
                            로그인
                        </button>
                        <p className="text-neutral-500 mt-12">
                            {variant === 'login' ? 'Netflix 회원이 아니신가요?' : '이미 회원이신가요?'}
                            <span onClick={toggleVariant} className="text-white ml-1 hover:underline cursor-pointer">
                                {variant === 'login' ? '지금 가입하세요' : '로그인 하세요'}
                            </span>
                        </p>
                    </div>
                </div>
            </div>
        </div>
    )
}

export default Login;

variant 는 login 또는 sign up 상태일 것이다. login 일때는 로그인 화면을 sign up 일때는 회원가입 화면을 보여준다. useState를 쓰면 해당 변수가 함수를 통해 변경이 되면 해당 변수가 포함된 영역을 모두 새로고침 해준다. 이를 통해서 하단의 흰색 문구를 누르면 즉시 페이지가 바뀔 수 있도록 코드를 짤 수 있었다.

 

<input> 과 <label> 그리고 peer에 대하여

 peer 는 Tailwind CSS에서 사용되는 유틸리티 클래스 중 하나로, 'peer' 클래스를 사용하여 형제 요소에 스타일을 적용하거나 관련된 요소 간에 스타일을 공유할 수 있다. 특히 'peer-placeholder-shown', 'peer-focus', 'peer-hover' 와 같은 상황에서 유용하게 사용된다고 한다. 나는 여기서 peer-placeholder-shown 과 peer-focus를 사용하였다. 이 둘은 말 그대로 폼 요소에서 placeholder 에 대한 스타일을 적용하거나, focus 된 상태의 스타일을 적용할 때 사용하는 것이다.

 이 둘을 활용하여 로그인 화면에 쓰일 Input 컴포넌트를 구현할 수 있었다.

 

import React from 'react';

interface InputProps {
    id: string
    description: string;
    secret: string;
    value: string;
    onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
};

export const Input : React.FC<InputProps> = ({ id, description, secret, value, onChange }) => {
    return (
        <>
        <div className='relative'>
            <input
            id={id}
            className='
            block
            rounded-md
            px-6
            pt-6
            pb-1.5
            w-full 
            text-md
            text-white
            bg-neutral-700 
            appearance-none
            focus:outline-none
            focus:ring-0
            peer
            '
            type={secret}
            value={value}
            onChange={onChange}
            placeholder=""
            /> 
            <label
            className='
            absolute
            text-md
            text-zinc-400 
            transform
            -translate-y-3
            scale-75
            top-3.5
            z-10
            origin-[0]
            left-6
            peer-placeholder-shown:scale-100
            peer-placeholder-shown:translate-y-0
            peer-focus:scale-75
            peer-focus:-translate-y-3
            '
            htmlFor={id}
            >{description}</label>
        </div>
        </>
    );
};

 

이번에 처음으로 Tailwind Css 를 쓰면서 느끼는 점은 처음써서 어렵고, 불편하고, 가독성이 떨어지는 느낌이 있다. 하지만 편리한 기능도 많고, 무엇보다 css-in-js 를 위한 코드를 다른곳에 추가로 작성하거나, 다른 파일을 추가하지 않아도 된다는 점에서 조금 더 좋은 점이 있는 것 같고, 사실 같은 화면에 있다는 사실이 가독성이 좋은 것 아닐까...?ㅋㅋㅋ

 

또 이전에는 그냥 div를 난발하면서 display : flex 해두고 어찌어찌 어거지로 위치를 맞췄던거 같은데 이번에 클론코딩을 하면서 여러가지 css를 활용하여 제대로 HTML + CSS 를 활용하는 프론트엔드 코딩을 진행하는거 같아서 뿌듯하다.

728x90