본문 바로가기
인턴IN메타/응용SW개발

[인턴IN메타] React 사용자 경험을 극대화하는 카드 등록 페이지 구현기 : CardContext 활용하기

by me_in_sk 2025. 7. 24.
반응형

카드 등록 페이지 구현기

지난 포스팅에서는 상품 목록에 '담기' 버튼을 추가하고, 무한 스크롤 기능과 다크/라이트 모드에 상관없이 명확한 시인성을 확보하는 작업을 다뤘습니다. 이번 주차에는 지난 '담기' 기능에 이어 '구매' 기능을 구현해 보았습니다. 사용자가 '구매' 버튼을 클릭하는 순간, 우리는 새로운 과제에 직면합니다. 바로 결제 페이지, 그중에서도 핵심인 '카드 등록' 기능입니다.

단순히 정보를 입력받는 폼(Form)을 넘어, 어떻게 하면 사용자가 안전하고 편리하게 자신의 결제 정보를 맡길 수 있을지 고민하는 것은 서비스의 신뢰도와 직결됩니다. 이번 3주차 인턴IN메타 챌린지에서는 고객의 요구사항을 기반으로 React를 활용하여 사용자 친화적인 카드 등록 페이지를 구현한 경험을 공유하고자 합니다.

 

최종 구현 모습

최종적으로 구현된 카드 등록 페이지의 모습
최종적으로 구현된 카드 등록 페이지의 모습


1. 요구사항 분석

성공적인 기능 개발의 첫걸음은 '무엇을 만들어야 하는가'를 명확히 정의하는 것입니다. 이번 결제 모듈 개발 역시 고객의 요구사항을 분석하고, 이를 구체적인 기능 목록으로 정리하는 것에서 시작했습니다.

 

요구사항

결제 모듈 개발을 위한 초기 요구사항 분석 및 기능 목록
인턴IN메타_응용SW_11일차 과업 중 일부 캡처

 

고객의 핵심 요구사항은 보안, 편의성, 직관성 세 가지로 요약할 수 있었습니다.

  • 보안 강화: 민감한 카드 정보는 안전하게 처리되어야 한다.
  • 간편한 카드 관리: 여러 카드를 쉽게 등록하고 관리할 수 있어야 한다.
  • 직관적인 UI/UX: 사용자가 헤매지 않고 카드 정보를 입력할 수 있어야 한다.

이러한 요구사항을 바탕으로 카드 번호, 만료일, CVC 등 각 필드에 대한 구체적인 유효성 검사 규칙과 UI/UX 개선 방안을 포함한 기능 목록을 확정했습니다.


2. '구매' 버튼에서 시작된 디테일의 여정

이번 프로젝트의 사전 작업으로는 기존 ProductCard 컴포넌트에 '구매' 버튼을 추가하는 것이었습니다. 여기서 중요한 UX 고려사항이 있었습니다. 저의 앱은 다크/라이트 모드를 지원하는데, 기존 '담기' 버튼은 테마에 따라 배경과 글씨색이 모두 변경됩니다. 하지만 핵심 액션 버튼인 '구매' 버튼은 어떤 모드에서든 사용자의 눈에 명확하게 띄어야 했습니다.

테마와 상관없이 일관된 스타일을 유지하는 '구매' 버튼
테마와 상관없이 일관된 스타일을 유지하는 '구매' 버튼

 

이를 위해 '구매' 버튼의 배경은 항상 노란색(#ffda00)으로, 텍스트는 가장 가독성이 높은 검은색(#111111)으로 고정했습니다. 이 작은 스타일링 결정이 바로 사용자 경험을 위한 디테일의 시작이라고 합니다.

 

ProductCard 컴포넌트 Update

// shooking/src/components/ProductCard.js

// '담기' 버튼을 상속받아 스타일을 덮어쓰는 '구매' 버튼
const PurchaseButton = styled(AddToCartButton)`
  background-color: #ffda00; /* 항상 노란색 */
  color: #111111; /* 항상 검은색 텍스트 */
`;

const ProductCard = ({ product }) => {
  const navigate = useNavigate();

  const handlePurchase = () => {
    // 구매 버튼 클릭 시 카드 목록 페이지로 이동
    navigate("/my-cards", { state: { productToPay: product } });
  };

  return (
    // ...
    <ButtonWrapper>
        <AddToCartButton>담기</AddToCartButton>
        <PurchaseButton onClick={handlePurchase}>구매</PurchaseButton>
    </ButtonWrapper>
    // ...
  );
};

 

3. 핵심 컴포넌트 설계와 구현 - UX 디테일의 중요성

요구사항을 기능으로 변환하는 과정에서, 저는 몇 가지 핵심 컴포넌트를 중심으로 페이지를 설계했습니다. 특히 사용자의 입력 경험을 향상시키는 데 집중했습니다.

 

1. 실시간 시각적 피드백: CardImage 컴포넌트

사용자가 폼에 정보를 입력할 때, 딱딱한 텍스트 필드만 바라보는 것은 지루하고 불안한 경험이 될 수 있습니다. 이를 해결하기 위해 사용자가 입력하는 정보가 실시간으로 반영되는 가상의 카드 이미지(CardImage) 컴포넌트를 구현했습니다.

 

사용자 입력에 따라 실시간으로 업데이트되는 CardImage 컴포넌트
사용자 입력에 따라 실시간으로 업데이트되는 CardImage 컴포넌트

 

사용자가 카드 번호를 입력하면 앞 8자리는 그대로 노출하고, 뒷자리는 보안을 위해 마스킹(•) 처리하여 CardImage에 즉시 렌더링합니다. 이름과 만료일 역시 마찬가지입니다. 이 작은 디테일 하나가 사용자에게 '내 정보를 올바르게 입력하고 있구나'라는 확신과 안정감을 줍니다.

 

CardImage 컴포넌트

// shooking/src/components/CardImage.js
const CardImage = ({ cardNumber, cardHolderName, expiryDate }) => {
  // 카드 번호를 받아 앞 8자리를 제외하고 마스킹 처리하는 로직
  const cleanCardNumber = cardNumber.replace(/\D/g, "");
  let displayCardNumber = "";

  for (let i = 0; i < 16; i++) {
    if (i > 0 && i % 4 === 0) displayCardNumber += " ";
    if (i < cleanCardNumber.length) {
      displayCardNumber += (i < 8) ? cleanCardNumber[i] : "•";
    } else {
      displayCardNumber += "•";
    }
  }

  return (
    <CardContainer>
      <Chip />
      <CardNumberDisplay>{displayCardNumber}</CardNumberDisplay>
      <CardInfoRow>
        <CardHolderName>{cardHolderName || "NAME"}</CardHolderName>
        <ExpiryDate>{expiryDate || "MM/YY"}</ExpiryDate>
      </CardInfoRow>
    </CardContainer>
  );
};

 

2. 복잡한 입력 처리: 상태 관리와 유효성 검사

카드 등록 페이지는 다양한 형식의 입력을 처리해야 합니다. 카드 번호(16자리 숫자), 만료일(MM/YY), CVC(3자리), 비밀번호(앞 2자리) 등 각 필드에 맞는 입력 처리와 실시간 유효성 검사가 필수적입니다.

AddCardPage.js 에서는 각 입력 필드에 대한 state를 두고, useEffect 훅을 사용해 입력값이 변경될 때마다 유효성을 검사하고 에러 메시지를 업데이트하도록 구현했습니다.

특히 까다로웠던 부분은 카드 번호비밀번호 입력 처리였습니다.

  • 카드 번호: 사용자가 보는 값(예: 1234-5678-••••-••••)과 실제 데이터로 저장되는 값(예: 1234567812345678)이 다릅니다. 이를 위해 rawCardNumber라는 별도의 state를 두어 순수 숫자 데이터는 따로 관리하고, 화면에는 포맷팅된 값을 보여주는 방식을 택했습니다.
  • 비밀번호: 각 자리마다 입력 필드를 분리하고, 한 글자 입력 시 다음 필드로 포커스가 자동으로 이동하도록 useRef를 활용했습니다. 이는 사용자의 입력 단계를 줄여주는 효과적인 UX 개선입니다.

직관적인 입력을 위해 분리된 비밀번호 필드와 자동 포커스 이동
직관적인 입력을 위해 분리된 비밀번호 필드와 자동 포커스 이동

 

모든 필수 정보가 유효하게 입력되었을 때만 '작성 완료' 버튼이 활성화되도록 제어하여, 잘못된 정보가 제출될 가능성을 원천적으로 차단했습니다.

 

3. 상태 관리의 중앙화: CardContext

새롭게 등록된 카드는 '카드 추가' 페이지뿐만 아니라, '등록된 카드 목록' 페이지 등 여러 컴포넌트에서 공유되어야 합니다. 이런 경우 props를 통해 데이터를 계속 넘겨주는 'Prop Drilling'은 코드를 복잡하게 만듭니다.

이를 해결하기 위해 React의 Context API를 사용했습니다. CardContext를 생성하여 등록된 카드 목록(registeredCards)과 카드 추가 함수(addCard)를 전역적으로 관리합니다. 이를 통해 어떤 컴포넌트에서든 useCards 훅 하나로 카드 데이터에 손쉽게 접근하고 상태를 변경할 수 있습니다.

CardContext 구현

// shooking/src/context/CardContext.js
import React, { createContext, useState, useContext } from "react";

const CardContext = createContext();

export const useCards = () => useContext(CardContext);

export const CardProvider = ({ children }) => {
  const [registeredCards, setRegisteredCards] = useState([]);

  const addCard = (card) => {
    setRegisteredCards((prevCards) => [
      ...prevCards,
      { ...card, id: Date.now() + Math.random() },
    ]);
  };

  const deleteCard = (id) => { /* ... */ };

  const value = { registeredCards, addCard, deleteCard };

  return <CardContext.Provider value={value}>{children}</CardContext.Provider>;
};

 


결론: 좋은 경험은 보이지 않는 곳에서 완성된다

카드 등록이 완료되면, 사용자는 자신이 등록한 카드 목록을 확인할 수 있는 CardListPage로 이동합니다. CardContext 덕분에 이 페이지는 별도의 데이터 요청 없이 전역 상태에 저장된 카드 목록을 즉시 보여줄 수 있습니다.

CardContext를 통해 전달받은 정보가 표시되는 등록된 카드 목록 페이지
CardContext 를  통해 전달받은 정보가 표시되는 등록된 카드 목록 페이지

 

이번 과제를 통해 가장 만족스러웠던 점은 인턴IN메타를 통해 '요구사항 분석'에서 시작하여 실제 '컴포넌트 설계 및 구현'까지 이어지는 실무적인 개발 프로세스를 온전히 경험했다는 것입니다. 특히 실시간 피드백을 제공하는 CardImage 컴포넌트나 입력 편의성을 높인 비밀번호 필드처럼, 사용자의 눈에는 당연해 보일 수 있는 작은 디테일들이 얼마나 깊은 고민과 기술적 구현을 필요로 하는지 다시 한번 깨달았습니다.

 

여러분이 결제 페이지를 만들 때 어떤 점을 가장 중요하게 생각하시나요? 댓글로 의견을 공유해주세요.

 

 


◆ 관련 링크

 

인턴IN메타

인턴IN메타(인턴인메타)는 인턴체험서비스를 제공하는 메타버스 직업체험관입니다.

www.interninmeta.or.kr

반응형

댓글