귀여운 눈높이에서 작성된, 🐰

하루 한 걸음씩, 성장 하는 중 입니다 🫶🏻

카테고리 없음

Main Project - 아이디 찾기 성공 화면 구현하기 (이메일 마스킹 처리)

creamymood 2025. 6. 29. 19:50

해야하는 것 

기존 아이디 찾기 모달에서 -> 회원 정보가 있을 시 

아이디찾기 폼 화면에서 -> 일치하는 아이디를 보여주는 화면

이렇게 회원정보를 보여주는 것을 구현 해야 한다.

 

아직 백엔드쪽이 준비가 안되서 UI 구현을 위한 가짜 로직 부터 필요하다.

 


흐름은 이런식으로

 

정리하자면,

 

성공 / 미성공 상태를 만들어서 

성공시 > 새로운 모달을 보여주는 조건부 렌더링

미성공시 > 오류 메시지 보여주는 것

 

이까지는 백엔드랑 연결되도 동일하고,

 

지금은 백엔드쪽이 없으니, 가짜 로직이 필요하니까..

우선 아이디를 저장해줄 상태도 만들어주고, 

이름이 가짜 로직인 " 홍길동 " 이랑 일치 할 경우, 가짜로 아이디를 저장해준 뒤,

보여줄 모달에서 그 이름을 보여주면 된다.

 

더보기

상황 정리

  • 사용자가 이름을 입력하고 "아이디 찾기" 버튼을 누름
  • 서버(또는 로컬 로직)에서 이름이 '홍길동'인지 확인
  • 맞으면 다른 UI(예: 성공 메시지, 아이디 보여주기 등)를 가진 모달을 보여주고
  • 틀리면 에러 메시지 보여주기

어떻게 구현할지

1) 상태로 성공 여부 관리하기

컴포넌트 내부에 success 상태를 만들어서 true/false로 결과를 관리하세요.

const [success, setSuccess] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [foundId, setFoundId] = useState(''); // 예시로 아이디 저장용

2) findId 함수 수정

const findId = (data) => {
  if (data.name === '홍길동') {
    setErrorMessage('');
    setFoundId('hong123'); // 예시 아이디
    setSuccess(true);
  } else {
    setErrorMessage('입력한 이름과 휴대폰 번호로 등록된\n 이메일이 존재하지 않습니다.');
    setSuccess(false);
  }
};

3) 조건부 렌더링으로 다른 UI 보여주기

return (
  <div>
    {success ? (
      <SuccessModalContent foundId={foundId} />
    ) : (
      <FindIdForm
        errorMessage={errorMessage}
        onSubmit={handleSubmit(findId)}
        register={register}
        errors={errors}
        watch={watch}
      />
    )}
  </div>
);

SuccessModalContent는 아이디 찾기 성공 시 보여줄 별도의 컴포넌트고,
FindIdForm은 기존 폼 UI 컴포넌트라고 생각하시면 돼요.


4) 컴포넌트 분리 예시

  • FindIdContent: 최상위 컴포넌트, 상태관리 및 조건부 렌더링 담당
  • FindIdForm: 입력 폼 UI
  • SuccessModalContent: 성공 시 보여줄 UI

정리하면

  • 서버(또는 로컬)에서 결과에 따라 상태 변경
  • 상태값(success 등)으로 렌더링 분기
  • 다른 UI를 보여주는 컴포넌트 분리하여 깔끔하게 관리

그런데 생기는 고민이..

새로 보일 컴포넌트를 분리하게 되면,

 

근데 상태는 드릴링 되야 되잖아 ..

 

이런거 고려하면 그냥 새 컴포넌트 SuccessModalContent 이거 말고 기존 FindIdContent 이 안에서 조건부 렌더링으로 보여주는게 나으련지.

 

기존은 아이디 찾기 화면인데, 틀리면 그냥 오류메세지, 맞으면 아이디를 보여주는 화면이 나을지 이게 고민이 되었다.

 

지피티의 조언은

더보기

말씀하신 흐름 완전히 맞습니다, 그리고 고민하신 부분이 아주 중요한 포인트예요.

흐름 다시 한번 확인

  • 상태 분리
    • success → 성공 여부
    • foundId → 찾은 아이디
    • errorMessage → 오류 메시지
  • 조건부 렌더링
    • 성공 시: 아이디 보여주는 UI
    • 실패 시: 에러 메시지
  • 백엔드 붙으면 동일, 지금은 가짜 로직으로 처리

 고민 포인트: SuccessModalContent를 새 컴포넌트로 분리할지, 내부 조건부로 할지

🔎 1) SuccessModalContent 컴포넌트로 분리하는 방식

  • 장점
    • UI 컴포넌트가 깔끔하게 분리
    • 재사용성 (나중에 다른 곳에서도 동일한 성공 모달이 필요할 때 사용 가능)
  • 단점
    • foundId를 props로 내려줘야 함 → 상태 드릴링 발생

 

 2) FindIdContent 안에서 조건부 렌더링하는 방식

{
  success ? (
    <div>
      <p>아이디 찾기 성공!</p>
      <p>당신의 아이디는: {foundId}</p>
      <button onClick={goLogin}>로그인하러 가기</button>
    </div>
  ) : (
    <>
      {/* 기존 아이디 찾기 폼 */}
      <form>
        ...
        {errorMessage && <p>{errorMessage}</p>}
        <CommonButton onClick={handleSubmit(findId)} text="아이디 찾기" />
      </form>
    </>
  )
}
  • 장점
    • 상태를 컴포넌트 안에 모두 유지 → 드릴링 문제 없음
    • 단순하고 빠르게 작성 가능
  • 단점
    • UI가 커질수록 컴포넌트가 복잡해짐

 결론: 지금 상황에 맞는 선택

👉 현재는 FindIdContent 안에서 조건부 렌더링하는 방식이 더 적합합니다!

  • 아직 재사용성 필요 없음
  • 단일 책임으로 간단히 유지 가능
  • 상태 드릴링 고민할 필요 X

 

우선 지금으로썬 재사용 할 곳이 없으니까..

일단 기존 FindId 컴포넌트에서 사용해보고, 추후에 리팩토링 때 분리 고민을 해보는게 맞을 것 같은데..

 

 

이렇게 결정하고 코드 작성 스타투


벌써 부터 고민 되는거..

버튼에 핸들러 달아줄 것.

로그인 버튼 누르면 > 다시 로그인으로 가게 하는 로직. (음.. 로그인 쪽은 페이지인데, 지금은 컴포넌트 인데..)

 

미리 고민이긴 하지만, 흐름은 이렇다고 한다

더보기

와, 정말 좋은 고민이에요!
이런 걸 미리 생각해두는 게 구조 설계에서 가장 중요한 습관입니다. 👍

💡 상황 정리

  • 지금 아이디 찾기는 모달 컴포넌트 (예: FindIdContent)
  • 로그인 페이지는 별도의 라우트 페이지
  • 성공 시 → "로그인 하러 가기" 버튼 클릭 → 로그인 페이지로 이동

🔎 어떻게 처리할까?

방법 1: 라우터 사용 (권장)

보통은 React Router를 쓰죠.
이 경우, 버튼 클릭 시 navigate('/login') 같은 식으로 페이지 이동을 하면 됩니다.

예시 (React Router v6 기준)

import { useNavigate } from 'react-router-dom'

function FindIdContent() {
  const navigate = useNavigate()

  const goLogin = () => {
    navigate('/login')
  }

  return (
    success ? (
      <div>
        <p>아이디 찾기 성공!</p>
        <p>당신의 아이디는: {foundId}</p>
        <button onClick={goLogin}>로그인하러 가기</button>
      </div>
    ) : (
      <>
        {/* 기존 폼 */}
      </>
    )
  )
}

 

방법 2: 부모에서 제어

만약 이 모달이 페이지 상위 컴포넌트에서 열리고, 상위에서 모달 열고 닫는 상태를 제어하고 있다면,
모달 닫기 + 라우트 이동을 동시에 할 수도 있습니다.

예시

const onGoLogin = () => {
  setShowFindIdModal(false) // 모달 닫기
  navigate('/login')       // 페이지 이동
}

이렇게 하면 "모달 닫기"와 "로그인 페이지 이동"을 동시에 처리할 수 있어요.

 방법 3: 링크 컴포넌트 사용

정말 단순하게 처리할 거라면 <Link to="/login">를 쓰는 방법도 있습니다.

import { Link } from 'react-router-dom'

<Link to="/login">
  <button>로그인하러 가기</button>
</Link>

근데 이건 버튼 스타일이 꼬일 수 있으니 보통은 navigate() 방식이 더 유연합니다.

 핵심 요약

  • 페이지 이동 = 라우터 관여 → navigate() 또는 <Link>
  • 모달 닫기 + 이동 → 부모에서 제어하거나 navigate() 안에서 상태 변경

우선 UI는 구현이 다 되었는데, 다른 구현 조건이 있다! 

이메일 부분의 일부를 마스킹 해야 하는 것

이메일 도메인 부분 마스킹 필요

 

백엔드 부분이 연결 되면 달라지는데, 우선 가짜 더미데이터로써 UI 구현중이니, 이대로 해본다

 

마스킹 처리 하는 방법은 함수 사용하는 것 !

더보기

아! 그러면 이메일에서 @ 뒤쪽(도메인 부분)의 일부를 마스킹하고 싶은 거네요?

예를 들어,

  • example.com 중에서 example은 보여주고 .com을 가리고 싶다거나
  • 아니면 ex****.com처럼 일부만 보여주고 싶다거나

그런 식으로요!


예시로 @ 뒤 도메인의 일부를 마스킹하는 함수 코드를 보여드릴게요.

function maskEmailDomain(email) {
  const [localPart, domain] = email.split("@");

  // domain 예: example.com
  // domain을 '.' 기준으로 나눠서 앞부분과 뒷부분 분리
  const [domainName, domainExt] = domain.split(".");

  // domainName은 앞 3글자만 남기고 나머지는 *로 마스킹
  const maskedDomainName = domainName.slice(0, 3) + "*".repeat(domainName.length - 3);

  return `${localPart}@${maskedDomainName}.${domainExt}`;
}

동작 예시

maskEmailDomain("user@example.com") 
// 결과: "user@exa****.com"

원리

  • example.com을 example과 com으로 분리
  • example의 앞 3글자 exa는 그대로 두고, 나머지 4글자 mple은 ****로 바꿈
  • user@ + exa**** + .com 형태로 출력

 

코드 중, 해당 부분 설명

const [localPart, domain] = email.split("@");
더보기

아, 여기서 쓰인 문법은 "배열 구조 분해 할당"(destructuring assignment)라는 자바스크립트 문법이에요.

쉽게 말하면, email.split("@")가 리턴하는 배열의 값을 각각 변수에 바로 넣는 방식이에요.

1. 먼저 email.split("@")는 뭐냐?

split 함수는 문자열을 특정 구분자 기준으로 나눠서 배열을 만들어요.

예를 들어,

const email = "user@example.com";
const parts = email.split("@");
console.log(parts); // ["user", "example.com"]

split("@")을 하면 "user@example.com"이 ["user", "example.com"]이라는 배열로 나뉘어요.

2. 배열 구조 분해 할당이 뭔가요?

배열 구조 분해 할당을 쓰면,

const [a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20

이렇게 배열의 첫 번째 값은 변수 a에, 두 번째 값은 변수 b에 바로 넣을 수 있어요.

3. 그래서

const [localPart, domain] = email.split("@");

const parts = email.split("@");
const localPart = parts[0];
const domain = parts[1];

이렇게 쓴 것과 똑같아요.

즉, 이메일에서 @ 기준으로 나눈 첫 번째 조각은 localPart에, 두 번째 조각은 domain에 저장된 거예요.

한마디로

  • email.split("@")가 ["user", "example.com"] 배열을 만든다.
  • 그 배열에서 첫 번째 값을 localPart에, 두 번째 값을 domain에 바로 넣는다.

이해가 좀 더 됐나요? 혹시 더 궁금하면 말해 주세요!

 

우선 ** 나중에 할, 백엔드 연결해서 이메일 받아온 뒤, 마스킹 처리 하는 로직 부터 간단히 공부 (스스로 겁먹지 말자는 취지에서)

더보기

 

실제로는 이렇게 한 번에 문자열로 오는 게 아니라, 백엔드에서 비동기로 데이터를 받아와서 상태(state)에 저장해야 합니다!

 상태(state)에 저장

React를 예로 들면, 보통 이런 식으로 씁니다:

const [email, setEmail] = useState("");

그리고 나서 서버에서 이메일을 받아오면:

setEmail(받아온_이메일값);

그럼, 이메일을 "받아오는" 코드 예제

예를 들어, fetch나 axios를 써서 백엔드 API에서 이메일을 가져온다고 해봅시다.

import { useEffect, useState } from "react";
import axios from "axios";

export default function FindEmailComponent() {
  const [email, setEmail] = useState("");

  useEffect(() => {
    // 여기에 실제 백엔드 API 주소를 넣습니다
    axios.get("/api/find-email?name=홍길동&phone=01012345678")
      .then((res) => {
        const receivedEmail = res.data.email;
        setEmail(receivedEmail); // 상태에 저장!
      })
      .catch((err) => {
        console.error("이메일 가져오기 실패!", err);
      });
  }, []);

  // 마스킹 처리
  function maskEmail(email) {
    const [localPart, domain] = email.split("@");
    if (localPart.length <= 2) {
      return localPart[0] + "***@" + domain;
    }
    const visible = localPart.slice(0, 2);
    return `${visible}***@${domain}`;
  }

  const maskedEmail = maskEmail(email);

  return (
    <div>
      <p>마스킹된 이메일: {maskedEmail}</p>
    </div>
  );
}

 

여기서 중요한 흐름

1️⃣ 백엔드에서 받아온다

👉 axios.get(...) 또는 fetch(...)

2️⃣ 받아온 이메일을 상태에 저장한다

👉 setEmail(receivedEmail)

3️⃣ 상태에 저장된 이메일을 마스킹한다

👉 maskEmail(email)

4️⃣ 마스킹된 이메일을 UI에 표시한다

👉 <p>{maskedEmail}</p>

왜 상태를 쓰는가?

  • 변수(const)는 한 번만 저장되고, 다시 렌더링되면 초기화됩니다.
  • 상태(useState)는 React가 기억하고, 값이 바뀌면 화면(UI)이 자동으로 다시 그려집니다.

→ 이게 바로 상태 관리(state management) 의 핵심이에요!

 

 

지금은 더미데이터로 할 거니까,

더보기

 

백엔드 준비가 안 됐을 때는, 임시로 더미 데이터를 컴포넌트 안에 넣어두고 테스트용으로 사용하는 게 아주 흔한 방법이에요.

예를 들어

function FindIdModal() {
  // 서버에서 받아온 이메일 대신 임시 이메일
  const emailFromServer = "exampleuser@gmail.com";

  function maskEmail(email) {
    const [localPart, domain] = email.split("@");
    const maskedLocal = localPart.slice(0, 3) + "****";
    return maskedLocal + "@" + domain;
  }

  const maskedEmail = maskEmail(emailFromServer);

  return (
    <div>
      <p>찾은 이메일: {maskedEmail}</p>
      <button>로그인하러 가기</button>
    </div>
  );
}

✅ 이렇게 하면:

  • 나중에 백엔드에서 실제 이메일을 받아오면 emailFromServer만 바꿔주면 됨.
  • 로직, UI 다 준비해놓고 백엔드 연결만 기다리면 돼서 훨씬 편함.

💡 정리

  • 지금처럼 더미 데이터로 마스킹 로직과 UI 먼저 만들어두는 거 → 완전 정석!
  • 추후에 서버 연결되면 그 자리만 교체하면 됨.

 

상황은 이렇고..

우리의 구현 목표는

이렇게 도메인의 일부를 마스킹 해야 하니까, 아래 함수처럼 사용 해야 한다.

function maskEmailDomain(email) {
  const [localPart, domain] = email.split("@");

  // domain 예: example.com
  // domain을 '.' 기준으로 나눠서 앞부분과 뒷부분 분리
  const [domainName, domainExt] = domain.split(".");

  // domainName은 앞 3글자만 남기고 나머지는 *로 마스킹
  const maskedDomainName = domainName.slice(0, 3) + "*".repeat(domainName.length - 3);

  return `${localPart}@${maskedDomainName}.${domainExt}`;
}

 

큰 흐름을 정리해보자면, 아이디 찾기 모달 안에서..

더미데이터를 마스킹해주는 함수를 만들어서, 그 함수를 보여주기만 하면 됌!


 

 

더보기

좋은 질문이에요! 정리해서 차근차근 설명드릴게요.


💡 지금 작성한 코드의 역할

const [foundId, setFoundId] = useState('') // 백엔드 연결 전 가짜 아이디 저장용

const findId = (data: any) => {
  console.log('제출된 데이터:', data);

  if (data.name === '홍길동') {
    setErrorMessage('');
    setSuccessFindId(true);
    setFoundId('hong123@ozcoding.com'); // 👉 여기서 가짜 이메일 저장
  } else {
    setErrorMessage('입력한 이름과 휴대폰 번호로 등록된\n 이메일이 존재하지 않습니다.');
    setSuccessFindId(false);
  }
};

현재 역할:

  • 아직 백엔드와 연결 안 됨
  • 임시로 홍길동일 때만 hong123@ozcoding.com을 성공 아이디로 세팅
  • 실패 시 에러 메시지 출력

💡 마스킹 로직은 별도로 관리

✔️ 지금 작성한 foundId는 원본 이메일을 저장하기 위한 값이에요.
✔️ 마스킹은 이 foundId 값을 UI에 보여줄 때 처리하는 게 깔끔합니다.

즉, foundId는 원본 보관, 화면에 뿌려줄 때만 마스킹 적용!
왜냐면:

  • 백엔드 연결 시 진짜 이메일이 들어오니까, 그걸 원본으로 가지고 있어야 함.
  • 마스킹 규칙이 바뀌어도 원본 데이터를 다시 가공해서 표시할 수 있음.

💡 제안 흐름

1️⃣ 지금 로직은 그대로 두기
2️⃣ 마스킹 함수 따로 작성
예:

function maskEmail(email) {
  const [local, domain] = email.split("@");
  const maskedLocal = local.slice(0, 3) + "****";
  return maskedLocal + "@" + domain;
}

3️⃣ UI 표시할 때만 마스킹 적용

<p>{maskEmail(foundId)}</p>

✅ 결론

  • ✔️ 기존 로직은 두고 (foundId에 원본 저장)
  • ✔️ 마스킹은 보여줄 때만 적용
  • ❌ 굳이 findId 로직 자체를 마스킹용으로 다시 짤 필요 없음

 

그러면, 이렇게 완성