Studying/미니 프로젝트

mini project - 영화 페이지 만들기(4-1단계) - 백엔드 연결 전 input창 구성하기 로그인 / 회원가입 구현

creamymood 2025. 5. 17. 23:50

모.. 모하나 쉬운게 없네.... 진챠 어렵다 .. .. .. .......... .... .인풋창.. 로그인.. 이렇게 어려운거 마자 .. ? 나만 그래 ,. ?

 


부캠에서 제시해준 구현단계 네번째 최종 구현 단계는 다음과 같습니다.


 

Supabase(백엔드)를 연결하기 전에, 우선 프론트만으로 로직을 구성해봅니다.


1. 회원가입/로그인 페이지 구현하기

기존 navBar에, 로그인 / 회원가입 칸은 따로 구성 해두었으니  공통 Input 컴포넌트를 만든 뒤 login 페이지와 Join 페이지

그 안을 채워줍니다.

 

 


유효성 검사? 프론트에서 구현할 수 있는 것 맞아 ? ↓

더보기

유효성 검사는 입력값의 형식이 적절한지를 판단하는 거야.

✔️ 유효성 검사 (Validation)

백엔드 없이도 가능 (프론트엔드에서 처리)

  • 비밀번호가 8자리 이상인가?
  • 이메일 형식이 맞는가? (@, .com 등)
  • 공백 없이 입력했는가?
  • 특수문자를 포함했는가?
if (password.length < 8) {
  // 유효성 검사 실패: "비밀번호는 8자리 이상이어야 해요"
}

✔️  인증 (Authentication)

 백엔드와의 통신 필요

  • 이 이메일/비밀번호 조합이 진짜 사용자 계정에 있는지?
  • 비밀번호가 정확한지?
// 백엔드에 보낸 후
if (서버응답 === "비밀번호 틀림") {
  // "비밀번호가 일치하지 않습니다" 표시
}

 

 

유효성 검사는 "형식이 맞는지" 보는 거고,
인증은 "실제로 맞는지" 보는 거야.


 "이 형식은 맞는 입력인가?" →  프론트에서 유효성 검사로 가능해!

 

 

유효성 검사 코드 예시↓

더보기

 

정규식 문법은 암기 X , 외우는 게 아니라 필요할 때 꺼내 쓰는 도구
전문가들도 다 외우고 쓰는 게 아니라, 상황에 맞게 구글링하거나 참고해서 조합

 

 

  • 자주 쓰는 조건은 패턴을 저장해놓기!
    • GitHub, Notion, 메모앱 등 정리해두면 나중에 금방 꺼내 쓸 수 있어.
    • 예: "비밀번호 8자리 + 숫자 포함" → 정리해두면 계속 재활용 가능!
  • 코드 작성할 때는 정규식 테스트 사이트 활용하기

비밀번호 조건에 따라 여러 가지 정규식이 있어.

 


기본: 8자리 이상

/^.{8,}$/
  • 설명: 문자 8개 이상이면 통과
  • 예: abc12345 → ✅ / abc123 → ❌

 영어+숫자 포함, 8자리 이상

/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/
  • 설명: 영문자 + 숫자 필수, 8자 이상
  • 예: password1 → ✅ / password → ❌ / 12345678 → ❌

 영어 대소문자 + 숫자 + 특수문자 포함, 8자리 이상

/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
  • 설명:
    • 소문자 1개 이상
    • 대문자 1개 이상
    • 숫자 1개 이상
    • 특수문자 1개 이상
    • 총 8자리 이상
  • 예: Abcdef1! → ✅ / abcdefg1 → ❌

예시 코드 (JavaScript)

const password = "Abcdef1!";
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;

if (regex.test(password)) {
  console.log("유효한 비밀번호입니다!");
} else {
  console.log("비밀번호 조건에 맞지 않습니다.");
}

 

 

내가 생각한 로직 ? ↓

일단 나는 유효성 검사를 두번 해야 한다고 생각했다. (구현 조건엔 없음)

1. 실시간 검사 2. 최종 검사

더보기

 


로그인 로직 흐름 요약

  1. 입력 실시간 유효성 검사 (프론트엔드)
    • 사용자가 아이디/비밀번호 입력할 때
    • 입력값의 형식 체크 (예: 이메일 형식인지, 비밀번호 길이는 적절한지 등)
    • 즉각적으로 에러 메시지 출력 (ex. "비밀번호는 8자 이상이어야 해요")
  2. 로그인 버튼 클릭 시, 최종 유효성 검사 (프론트엔드)
    • 사용자가 입력을 다 마치고 버튼 클릭 시
    • 한 번 더 전체적으로 조건 검토 (입력 안 된 필드가 없는지 등)
    • 유효하지 않으면 서버로 요청 보내지 않음
  3. 서버 요청 (백엔드)
    • 유효하면 서버로 로그인 정보 전송 (API 요청)
    • 백엔드는 실제 사용자 정보(DB)와 비교하여 로그인 성공 여부 판단
    • 로그인 성공 → 토큰 발급, 유저 정보 전달 등
    • 로그인 실패 → 오류 메시지 전달 (예: "비밀번호가 틀렸습니다")
  4. 프론트에서 서버 응답 기반 렌더링
    • 성공 시: 메인 페이지로 이동
    • 실패 시: 서버로부터 받은 메시지를 사용자에게 보여줌

 

 

해당 사진은, 유효성 검사 외에 버튼을 클릭 했을 때, 서버로 로그인 요청을 보내는 것

 


 

* 같이 공부하시는 비스킷 동기님의 조언 : 유효성 검사 쪽 로그인 / 회원가입 창에서 직접 해야 함.

( 직접 안하면, 검사 후 예를 들어 틀렸어도, 로그인할 때 그냥 넘어간다고 함!) ->

이 부분은 백엔드쪽이랑도 관리가 있어서?(모르겠음) 아직 내 머리로는 생각이 어려움

더보기

유효성 검사 로직을 로그인 페이지, 회원가입 페이지, 프로필 수정 페이지 등 각기 다른 페이지에서 매번 새로 다 구현하는 건 비효율적이야. 그래서 보통은 검사 함수(또는 유틸)를 공통으로 만들어서 재사용해.


방법 1. 공통 유효성 검사 함수 만들기 (모듈화)

예를 들어 validation.js 같은 파일에 함수들을 만들어두고, 로그인, 회원가입 페이지에서 필요할 때 불러다 써.

// validation.js
export function validateEmail(email) {
  if (!email) return "이메일을 입력해주세요.";
  if (!email.includes("@")) return "이메일 형식이 올바르지 않습니다.";
  return "";
}

export function validatePassword(password) {
  if (!password) return "비밀번호를 입력해주세요.";
  if (password.length < 6) return "비밀번호는 6자리 이상이어야 합니다.";
  return "";
}

 

각 페이지에서

import { validateEmail, validatePassword } from "./validation";

function handleSubmit() {
  const emailError = validateEmail(email);
  const passwordError = validatePassword(password);
  // 에러 있으면 처리...
}

방법 2. 커스텀 훅으로 만들기 (React 전용)

React에서 자주 쓰는 패턴인데, useValidation 같은 훅을 만들어서 관리하면 편해요.

function useValidation() {
  function validateEmail(email) { /*...*/ }
  function validatePassword(password) { /*...*/ }

  return { validateEmail, validatePassword };
}

그리고 각 컴포넌트에서 훅을 불러서 씁니다.


서버와 통신하는 검사

서버 통신이 필요한 중복 검사 같은 건

  • 프론트에서 공통 함수로 API 호출하는 부분만 만들거나
  • 각 페이지에서 별도로 API 호출하고, 결과 받아서 처리해도 괜찮아

다만 API 호출 로직도 api.js 같은 모듈에 정리해두는 게 유지보수에 좋아요.


요약

  • 기본 형식 검사(유효성 검사)는 공통 함수 또는 훅으로 만들어 재사용
  • 서버와 통신하는 중복 검사 등은 페이지별로 상황에 맞게 구현해도 되지만, API 호출 함수는 공통 관리 추천
  • 이렇게 하면 코드 중복 줄이고, 관리가 훨씬 쉬워짐

supabase 연동 전 로그인 전체 코드

더보기
// Login.jsx
import React, { useState } from "react";
import Input from "../components/Input";
import { Navigate, useNavigate } from "react-router-dom";


function Login() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [emailError, setEmailError] = useState("");
  const [passwordError, setPasswordError] = useState("");
  const navigate = useNavigate();



  // 이메일 검사 함수
  const validateEmail = (email) => {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  };

  // 비밀번호 검사 함수
  const validatePassword = (password) => {
    return password.length >= 6;
  };

  // 이메일 입력할 때마다 검사
  const handleEmailChange = (e) => {
    const value = e.target.value;
    setEmail(value);

    if (!validateEmail(value)) {
      setEmailError("올바른 이메일 형식이 아니에요.");
    } else {
      setEmailError("");
    }
  };

  // 비밀번호 입력할 때마다 검사
  const handlePasswordChange = (e) => {
    const value = e.target.value;
    setPassword(value);

    if (!validatePassword(value)) {
      setPasswordError("비밀번호는 6자 이상이어야 해요.");
    } else {
      setPasswordError("");
    }
  };

  
  
  const handleLogin = () => {
    if (email === "test@test.com" && password === "123456") {
      alert("로그인 성공");
      navigate("/");
    } else {
      alert("아이디 또는 비밀번호가 잘못되었습니다.");
    }
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
  
    if (!emailError && !passwordError && email && password) {
      handleLogin();
    } else {
      alert("입력한 값을 다시 확인해주세요.");
    }
  };
  

  return (
    <div style={{ maxWidth: "400px", margin: "2rem auto" }}>
      <h2>로그인</h2>
      <form onSubmit={handleSubmit}>
        <Input
          label="이메일"
          type="email"
          value={email}
          onChange={handleEmailChange}
          error={emailError}
        />
        <Input
          label="비밀번호"
          type="password"
          value={password}
          onChange={handlePasswordChange}
          error={passwordError}
        />
        <button
          type="submit"
          style={{
            padding: "0.5rem 1rem",
            backgroundColor: "#333",
            color: "white",
            border: "none",
            borderRadius: "4px",
            cursor: "pointer"
          }}
        >
          로그인
        </button>
      </form>
    </div>
  );
}

export default Login;

 

supabase 연동 전 회원 가입 전체 코드

더보기
//Join.jsx
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import Input from "../components/Input";

function Join() {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    const [name, setName] = useState("");
    const [confirmPassword, setConfirmPassword] = useState("");
    const [emailError, setEmailError] = useState("");
    const [passwordError, setPasswordError] = useState("");
    const [nameError, setNameError] = useState("");
    const [confirmPasswordError, setConfirmPasswordError] = useState("");


    const navigate = useNavigate();
  
  
  
    // 이메일 검사 함수
    const validateEmail = (email) => {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      return emailRegex.test(email);
    };

    // 이름 검사 함수
    const validateName = (name) => {
        // 2~8자 사이, 숫자, 한글, 영어만 허용
        const nameRegex = /^[가-힣a-zA-Z0-9]{2,8}$/;
        return nameRegex.test(name);
      };
      
  
    // 비밀번호 검사 함수
    const validatePassword = (password) => {
      return password.length >= 6;
    };

    //비밀번호 확인 검사 함수
    const validatePasswordConfirm = (passwordConfirm) => {
        return passwordConfirm === password;
      };
      



    // 이메일 입력할 때마다 검사
    const handleEmailChange = (e) => {
      const value = e.target.value;
      setEmail(value);
  
      if (!validateEmail(value)) {
        setEmailError("올바른 이메일 형식이 아니에요.");
      } else {
        setEmailError("");
      }
    };


    // 이름 입력할 때마다 검사
    const handleNameChange = (e) => {
        const value = e.target.value;
        setName(value);
    
        if (!validateName(value)) {
          setNameError("올바른 이름 형식이 아니에요.");
        } else {
          setNameError("");
        }
      };
  
    // 비밀번호 입력할 때마다 검사
    const handlePasswordChange = (e) => {
      const value = e.target.value;
      setPassword(value);
  
      if (!validatePassword(value)) {
        setPasswordError("비밀번호는 6자 이상이어야 해요.");
      } else {
        setPasswordError("");
      }
    };

    // 비밀번호 확인 부분 입력할 때마다 검사
    const handleConfirmPasswordChange = (e) => {
        const value = e.target.value;
        setConfirmPassword(value);
    
        if (!validatePasswordConfirm(value)) {
          setConfirmPasswordError("비밀번호가 일치하지 않습니다.");
        } else {
          setConfirmPasswordError("");
        }
      };
    
  
    
    
    const handleJoin = () => {
      if (!emailError && !passwordError && !nameError && email && password && name && confirmPassword && !confirmPasswordError) {
        alert("회원가입 성공, 로그인 창으로 넘어갑니다.");
        navigate("/login");
      } 
    };
    
    const handleSubmit = (e) => {
      e.preventDefault();
    
      if (!emailError && !passwordError && !nameError && email && password && name && confirmPassword && !confirmPasswordError) {
        handleJoin();
      } else {
        alert("입력한 값을 다시 확인해주세요.");
      }
    };
    
  
    return (
      <div style={{ maxWidth: "400px", margin: "2rem auto" }}>
        <h2>회원가입</h2>
        <form onSubmit={handleSubmit}>
          <Input
            label="이메일"
            type="email"
            value={email}
            onChange={handleEmailChange}
            error={emailError}
          />
          <Input
            label="이름"
            type="name"
            value={name}
            onChange={handleNameChange}
            error={nameError}
          />
          <Input
            label="비밀번호"
            type="password"
            value={password}
            onChange={handlePasswordChange}
            error={passwordError}
          />

        <Input
            label="비밀번호 확인"
            type="password"
            value={confirmPassword}
            onChange={handleConfirmPasswordChange}
            error={confirmPasswordError}
                />


          <button
            type="submit"
            style={{
              padding: "0.5rem 1rem",
              backgroundColor: "#333",
              color: "white",
              border: "none",
              borderRadius: "4px",
              cursor: "pointer"
            }}
          >
            회원가입
          </button>
        </form>
      </div>
    );
  }

export default Join;

 


2. 앞선 login 페이지에서, 우선 백엔드 없이 로그인, 회원가입을 구현할 수 있는 코드를 작성했고,

   이제 로그인 된 뒤 NavBar의 UI를 바꿔 봐야 합니다.

로그인이 되었다는 걸 기억할 로그인유무 상태가 필요하고,

그 상태 따라 조건부 렌더링 해준다.

 

자 그런데 이제 또 상태는 어느 컴포넌트 관리해야하고 로그인에 따른, NavBar 렌더링은 어디서 해야하지?
렌더링은 네브바에서 할테니까 네브바에서 렌더링 코드 짜면되고..
상태는.. 

 

더보기

지금 단계에서 "상태를 어디서 관리하고, 렌더링은 어디서 할지" 정확히 짚고 넘어가는 건 아주 좋아!

 상태는 어디서 관리해야 할까?

💡 로그인 상태 (isLoggedIn)는 여러 컴포넌트에서 필요하니까 → _상위 컴포넌트_에서 관리하는 게 좋아.

  • 예: App.js 혹은 Layout, Root 같은 상위 컴포넌트
  • 그래야 Navbar 같은 하위 컴포넌트로 props로 전달해서 쓸 수 있어

 구조 예시

 App.js (or Layout.js)

import React, { useState } from "react";
import Navbar from "./components/Navbar";
import { Routes, Route } from "react-router-dom";

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <>
      <Navbar isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} />
      <Routes>
        {/* 로그인 시 setIsLoggedIn(true) 해줄 수 있게 prop 전달 */}
        <Route path="/login" element={<Login setIsLoggedIn={setIsLoggedIn} />} />
        {/* 기타 라우트 */}
      </Routes>
    </>
  );
}

export default App;

Navbar.js

function Navbar({ isLoggedIn, setIsLoggedIn }) {
  return (
    <nav>
      <Logo />
      <SearchInput />
      {isLoggedIn ? (
        <ThumbnailMenu setIsLoggedIn={setIsLoggedIn} />
      ) : (
        <>
          <Link to="/login">로그인</Link>
          <Link to="/join">회원가입</Link>
        </>
      )}
    </nav>
  );
}

Login.js (로그인 성공 후)

function Login({ setIsLoggedIn }) {
  const navigate = useNavigate();

  const handleLogin = () => {
    // 로그인 검증 로직 (생략)
    setIsLoggedIn(true); // 로그인 성공 시 상태 업데이트
    navigate("/");       // 홈으로 이동
  };

  return (
    // 로그인 폼 ...
    <button onClick={handleLogin}>로그인</button>
  );
}

 

 

ThumbnailMenu.jsx(로그인 후, 썸네일 컴포넌트. 마이페이지와, 로그아웃 버튼)

import React from "react";

function ThumbnailMenu({ setIsLoggedIn }) {
  const handleLogout = () => {
    setIsLoggedIn(false); // 로그아웃 시 상태 변경
    alert("로그아웃되었습니다.");
  };

  return (
    <div style={{ position: "relative" }}>
      <img
        src="/user-thumbnail.jpg"
        alt="User"
        style={{ width: "40px", borderRadius: "50%", cursor: "pointer" }}
      />
      <div
        style={{
          position: "absolute",
          top: "50px",
          right: 0,
          background: "white",
          border: "1px solid #ddd",
          borderRadius: "6px",
          padding: "10px"
        }}
      >
        <button onClick={() => alert("마이페이지로 이동")}>마이페이지</button>
        <button onClick={handleLogout}>로그아웃</button>
      </div>
    </div>
  );
}

export default ThumbnailMenu;

 

상태를 공유해야하는데 context API나 리덕스를 아직 쓰지 않으면 최상단에서

App
└── Layout (props 받아서)
    └── Navbar (또 props 받아서)
        └── ThumbnailMenu (또또 props 받아서)

이런식으로 내려줘도 된다. 

작은 프로젝트에서는 괜찮음! (드릴링...)


짜잔~ CSS는 엉망이지만, 로그인에 따라 이제 썸네일 UI 구현 완료 ~


혼자 해 본 심화 공부

유효성 검사 파트 중에,

특정 사이트에서는 사용자가 회원가입시 아이디를 입력할 때, 실시간으로 데이터를 받아오는건지.. 에러메시지에

"이미 사용중인 아이디 입니다." 이런식으로 뜰 때가 있다.

 

이런 경우.. 프론트로의 유효성 검사만으로는 안될 것 같다는 생각을 해봤음.

지금으로써는 많이 무리일 것 같고, 나중에 언젠가 이 기능도 넣어보고 싶다 !

더보기

 그게 바로 입력할 때마다 서버에 중복 검사 요청을 보내는 실시간 중복 검사야.


어떻게 동작하냐면?

  1. 사용자가 아이디를 입력할 때마다 (예: onChange 또는 onBlur 이벤트 때)
  2. 그 입력값을 서버에 API 요청으로 보내서
  3. 서버가 DB에서 중복 여부를 검사한 뒤
  4. 결과를 받아서 바로 화면에 “중복 아이디입니다” 또는 “사용 가능한 아이디입니다” 라고 보여주는 거야

예시 코드 (간단하게)

function SignUpForm() {
  const [username, setUsername] = React.useState("");
  const [error, setError] = React.useState("");

  async function checkUsername(name) {
    if (!name) {
      setError("");
      return;
    }

    const response = await fetch("/api/check-username", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ username: name }),
    });
    const data = await response.json();

    if (data.exists) {
      setError("중복 아이디입니다.");
    } else {
      setError("");
    }
  }

  function handleUsernameChange(e) {
    const value = e.target.value;
    setUsername(value);
    checkUsername(value);  // 입력할 때마다 서버 중복 검사 호출
  }

  return (
    <div>
      <input value={username} onChange={handleUsernameChange} />
      {error && <div style={{color: "red"}}>{error}</div>}
      {/* 가입 버튼 등 */}
    </div>
  );
}

단점도 있어!

  • 입력할 때마다 서버에 요청 보내니까 너무 잦으면 서버 부담 커짐
  • 그래서 **"디바운스(Debounce)"**라는 기법을 사용해서
    • 사용자가 입력을 멈추고 잠시 후에만 검사 요청 보내도록 조절하기도 함

 

 

비스킷 동기님이 언급하신 거랑 비슷하게, 예를 들어 회원가입이나 로그인하기 버튼을 눌렀을 때

백엔드 부분과 연결되어 회원가입이나 로그인을 시도한 특정 회원의 DB에 따라 또 다르게 렌더링 해줘야 할 것 같았다.

이것도 나중에 꼭 로직으로 넣어줘야 할 부분이겠지 ?

더보기

- 실시간 검사(프론트에서 바로 검사)

- 최종 검사(서버에서 검사)

 

여기서 최종 검사는 보통 이렇게 진행해요:


1. 프론트에서는 기본적인 형식 검사만 한다

예를 들어, 이메일 형식, 비밀번호 길이, 필수 입력 여부 같은 건 프론트에서 바로 검사해서 유저에게 빠르게 알려줌.


2. 최종 검사 (서버 검사)

폼 제출 버튼 눌렀을 때, 프론트에서 서버(API)로 데이터 보냄 → 서버에서 DB나 비즈니스 로직 기반으로 중복 여부, 실제 가입 가능한지, 권한 체크 등 더 엄격한 검사를 함


3. 서버 응답 결과에 따라 에러 메시지 보여주기

  • 서버에서 이상 없으면 가입 성공 처리
  • 서버에서 에러(예: 이메일 중복, 유효하지 않은 데이터) 반환하면, 프론트에서 조건부 렌더링으로 해당 에러 메시지 보여줌

요약

  • 실시간 검사는 프론트에서 빠르게 UI 반응
  • 최종 검사는 서버 쪽에서 꼭 해줘야 하는 중요한 검증
  • 서버에서 받은 결과에 따라 프론트에서 에러 메시지를 보여주는 방식이 일반적이에요

 

 

따로 설정하지 않았는데, email Input창에서 

이렇게 뜨는데, 이건 ↓

더보기

그 "@"를 포함하라고 뜨는 경고 메시지"는 사실 우리가 따로 설정한 게 아니고,
브라우저(크롬, 사파리 등)가 자동으로 해주는 기본 기능이야.

 

우리가 Input.jsx에서 <input type="email" /> 이렇게 썼잖아?

<input
  type={type} // 여기에 email이 들어가 있음
  ...
/>
  • type="email" 은 HTML에서 기본으로 제공하는 "유효성 검사" 기능이 있어.
  • 그래서 이메일 주소에 @ 없으면 브라우저가 자동으로 경고를 띄우는 거야.
  • 이건 자바스크립트 코드랑은 상관없이 브라우저가 제공하는 기능이야.

 그럼 이걸 끄고 싶으면?

<form> 태그에 noValidate라는 속성을 넣어주면 돼!

<form onSubmit={handleSubmit} noValidate>

이렇게 하면 브라우저가 자동으로 검사하는 걸 꺼버리고,
우리가 만든 validateEmail() 함수만 작동하게 돼.

 

 

썸네일 부분 메뉴 호버로

더보기

그럼 마우스를 썸네일에 올렸을 때만 "마이페이지 / 로그아웃" 메뉴가 뜨는
ThumbnailMenu 컴포넌트를 만들어보자!

 

해결 방법

썸네일 이미지 + 메뉴 전체를 감싸는 영역에 onMouseEnter, onMouseLeave를 줘야
메뉴 안에 마우스를 내려도 안 사라져!


 수정 버전

import React, { useState } from "react";

function ThumbnailMenu({ setIsLoggedIn }) {
  const [isOpen, setIsOpen] = useState(false);

  const handleLogout = () => {
    setIsLoggedIn(false);
    alert("로그아웃 되었습니다.");
  };

  return (
    <div
      style={{ position: "relative", display: "inline-block" }}
      onMouseEnter={() => setIsOpen(true)}
      onMouseLeave={() => setIsOpen(false)}
    >
      {/* 썸네일 이미지 */}
      <img
        src="/thumbnail.png"
        alt="User Thumbnail"
        style={{
          width: "40px",
          height: "40px",
          borderRadius: "50%",
          cursor: "pointer",
        }}
      />

      {/* 썸네일과 메뉴 전체를 포함한 영역이 hover 상태일 때만 메뉴 표시 */}
      {isOpen && (
        <div
          style={{
            position: "absolute",
            top: "50px",
            right: 0,
            backgroundColor: "#fff",
            border: "1px solid #ccc",
            borderRadius: "8px",
            boxShadow: "0 2px 6px rgba(0,0,0,0.15)",
            padding: "0.5rem",
            zIndex: 100,
            width: "120px",
          }}
        >
          <button
            style={{
              display: "block",
              padding: "0.5rem 1rem",
              background: "black",
              color: "white",
              border: "none",
              width: "100%",
              textAlign: "left",
              cursor: "pointer",
            }}
            onClick={() => alert("마이페이지로 이동")}
          >
            마이페이지
          </button>
          <button
            style={{
              display: "block",
              padding: "0.5rem 1rem",
              background: "black",
              color: "white",
              border: "none",
              width: "100%",
              textAlign: "left",
              cursor: "pointer",
            }}
            onClick={handleLogout}
          >
            로그아웃
          </button>
        </div>
      )}
    </div>
  );
}

export default ThumbnailMenu;