Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2단계 - 페이먼츠] - 버건디(전태헌) 미션 제출합니다. #371

Merged
merged 57 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
44f4305
chore : 리액트 라우터 돔 설치
brgndyy Apr 25, 2024
12e5705
docs: README.md 작성
brgndyy Apr 25, 2024
14bfb38
chore: 타입 모듈 설정을 위해 tsconfig.json 수정
brgndyy Apr 25, 2024
1007a94
refactor: step1때 구현했던 컴포넌트들 파일로 분리후 새롭게 생성
brgndyy Apr 25, 2024
854bc68
refactor: Input forwardRef 연결
brgndyy Apr 25, 2024
9bbcf05
feat: 공용 버튼 컴포넌트 생성
brgndyy Apr 25, 2024
86aecc8
feat: 분기 렌더링, 카드 이미지 변경 커스텀 훅 생성
brgndyy Apr 25, 2024
127b5a1
feat: 추가되는 등록 컴포넌트 생성
brgndyy Apr 25, 2024
c5c7539
chore: tsconfig.json 수정
brgndyy Apr 25, 2024
5be52a4
feat: cvc 컴포넌트 생성
brgndyy Apr 25, 2024
b43c38c
refactor: useCardNumber 훅 수정
brgndyy Apr 25, 2024
dd79524
refactor: useInput 훅 수정
brgndyy Apr 25, 2024
934a07b
feat: 확인 페이지 작성
brgndyy Apr 25, 2024
19c0c96
feat: 상수 추가 및 타입 경로 수정
brgndyy Apr 25, 2024
499c5ed
feat: 유효성 검사 함수 추가
brgndyy Apr 25, 2024
d803c5a
feat: 완료 버튼 렌더링 관련 커스텀 훅 생성
brgndyy Apr 25, 2024
5bcb4d4
feat: 메인 컴포넌트에서 바인딩
brgndyy Apr 25, 2024
de53040
feat: 공용 셀렉트 옵션 컴포넌트 생성
brgndyy Apr 25, 2024
5394502
feat: 스토리북 작성
brgndyy Apr 25, 2024
d407d18
feat: 404 페이지와 에러페이지 작성
brgndyy Apr 25, 2024
284be3d
refactor: 라우트 상수 처리 및 그외 스타일 수정
brgndyy Apr 25, 2024
c4cec96
chore: 스토리북 addon 추가
brgndyy Apr 25, 2024
741bb32
refactor: 상수 경로 수정
brgndyy Apr 25, 2024
e2e01e7
refactor: 린트 에러 수정
brgndyy Apr 25, 2024
4ce799e
refactor: useDetectComplete 커스텀 훅 유효성 검사 함수 유틸로 분리
brgndyy Apr 27, 2024
ccda0d0
refactor: 타입 설정 폴더 및 파일 변경
brgndyy Apr 27, 2024
6443a52
chore: 경로 alias 적용
brgndyy Apr 27, 2024
919f76c
refactor: 버튼 공용 타입 변경
brgndyy Apr 27, 2024
0b048d5
refactor: 타입 alias 적용
brgndyy Apr 27, 2024
976bcf3
refactor: 카드 관련 스타일 파일 분리
brgndyy Apr 27, 2024
a03d0b1
refactor: 카드 관련 상수 파일명 변경
brgndyy Apr 27, 2024
0b86c01
feat: 체크 아이콘 이미지 svg 컴포넌트 생성
brgndyy Apr 27, 2024
c9b777d
feat: useExpirationDate 커스텀훅 적용
brgndyy Apr 28, 2024
a06770e
refactor: 카드 등록 섹션 컴포넌트명 변경
brgndyy Apr 28, 2024
a17df96
fix: 피그마 시안과 같도록 확인페이지 수정
brgndyy Apr 28, 2024
35646fe
refactor: 카드등록 페이지 컴포넌트로 따로 분리
brgndyy Apr 28, 2024
ce578f0
refactor: 에러페이지에서 라우팅 경로 메인으로 변경
brgndyy Apr 28, 2024
4f54fb3
refactor: NotFoundPage 상태 수정
brgndyy Apr 28, 2024
fef7cc4
chore: type alias 적용
brgndyy Apr 28, 2024
32fe954
feat: 확인 페이지 라우팅 버튼 컴포넌트 생성
brgndyy Apr 28, 2024
7a6c6e0
refactor: App과 main 에서 컴포넌트 분리
brgndyy Apr 28, 2024
8efe68e
refactor: 카드 정보를 담고 있는 상수 이름 수정
brgndyy Apr 28, 2024
760d360
refactor: isValidCurrentStep 상태명 수정
brgndyy Apr 28, 2024
3c4ded7
feat: 월 관련 상수 추가
brgndyy Apr 28, 2024
5e291c9
refactor: InputSection 컴포넌트 폴더 위치 수정
brgndyy Apr 28, 2024
bfee2dd
refactor: nextStepHandler onComplete 로 수정
brgndyy Apr 28, 2024
9f28ef1
refactor: useDetectComplete 훅 삭제
brgndyy Apr 28, 2024
82f00ef
refactor: useCardIssuer 훅 수정
brgndyy Apr 28, 2024
de5b6f8
refactor: 스토리북 재작성
brgndyy Apr 28, 2024
c2dee55
refactor: ref 인자 수정
brgndyy Apr 28, 2024
06e1030
refactor: useCardNumber 훅 수정
brgndyy Apr 28, 2024
f1f7ece
refactor: forwardRef 수정
brgndyy Apr 28, 2024
c4ea12f
refactor: 타입 추가
brgndyy Apr 28, 2024
002f8c1
fix: 셀렉트 태그 placeholder 추가
brgndyy Apr 28, 2024
8e3398c
refactor: 컨펌페이지 props 수정
brgndyy Apr 28, 2024
4262ed6
refactor: 등록섹션 관련 타입 변경
brgndyy Apr 29, 2024
8e217b2
refactor: index.html lang 속성 변경
brgndyy Apr 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 10 additions & 9 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import type { StorybookConfig } from "@storybook/react-vite";
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
'@storybook/addon-onboarding',
'@storybook/addon-links',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
'storybook-addon-remix-react-router',
],
framework: {
name: "@storybook/react-vite",
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: "tag",
autodocs: 'tag',
},
};
export default config;
25 changes: 25 additions & 0 deletions README-STEP2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 동적 입력 UI 구현

사용자는 카드 번호를 입력할 때 동적으로 제공되는 입력 UI를 통해 집중적으로 하나의 입력 필드에만 집중할 수 있다.

# 카드사 선택

사용자는 카드사를 선택할 수 있고, 카드사에 따라 미리보기 카드의 색상을 변경한다.

# CVC 번호

CVC 번호를 입력할 때는 미리보기 카드의 뒷면을 시각적으로 보여준다.

입력은 숫자만 가능하며, 유효하지 않은 값을 입력 시 피드백을 제공한다.

# 폼 제출 및 상태 관리

모든 카드 정보가 정확하게 입력되고 검증되었을 때 확인 버튼이 활성화된다.

사용자가 입력한 정보 중 하나라도 지우거나 수정하여 유효하지 않게 되면, 확인 버튼은 보이지 않는다.

# 카드 등록 완료 및 네비게이션

확인 버튼을 클릭하면 사용자는 카드 등록 완료 페이지로 이동한다.

카드 등록 완료 페이지에서는 카드 등록이 성공적으로 완료되었다는 메시지와 함께, 다시 카드 등록 페이지로 돌아갈 수 있는 확인 버튼을 제공한다.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.23.0",
"styled-components": "^6.1.8",
"styled-reset": "^4.5.2"
},
Expand Down Expand Up @@ -44,6 +45,7 @@
"eslint-plugin-storybook": "^0.8.0",
"prettier": "^3.2.5",
"storybook": "^8.0.8",
"storybook-addon-remix-react-router": "^3.0.0",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
Expand Down
156 changes: 3 additions & 153 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,161 +1,11 @@
import GlobalStyles from './GlobalStyles';
import InputInfo from './components/InputSection';
import Input from './components/composables/Input';
import CreditCard from './components/CreditCard';
import useInput from './hooks/useInput';
import Label from './components/composables/Label';
import validate from './utils/validate';
import { CARD_NUMBER, EXPIRATION_PERIOD, OWNER_NAME } from './constants/cardSection';
import * as React from 'react';
import useCardNumber, { InitialCardNumberState } from './hooks/useCardNumber';
import * as S from './app.style';

const initialCardNumberState: InitialCardNumberState = {
value: '',
isError: false,
};

const CARD_NUMBER_LENGTH = 4;

const MONTH = Object.freeze({
MIN: 1,
MAX: 12,
});

const MAX_LENGTH = Object.freeze({
CARD_NUMBERS: 4,
MONTH: 2,
YEAR: 2,
NAME: 30,
});

function App() {
const { cardNumbers, cardNumbersChangeHandler, cardBrand } = useCardNumber(
Array.from({ length: CARD_NUMBER_LENGTH }, () => initialCardNumberState),
);

const {
inputValue: month,
onChange: monthChangeHandler,
error: monthError,
} = useInput([
{
fn: (value) =>
validate.isNumberInRange({ min: MONTH.MIN, max: MONTH.MAX, compareNumber: Number(value) }),
},
{ fn: (value) => validate.isValidDigit(value) },
]);

const {
inputValue: year,
onChange: yearChangeHandler,
error: yearError,
} = useInput([{ fn: (value) => validate.isValidDigit(value) }]);

const {
inputValue: name,
onChange: nameChangeHandler,
error: nameError,
} = useInput([{ fn: (value) => validate.isEnglish(value) }]);
import { PropsWithChildren } from 'react';

function App({ children }: PropsWithChildren) {
return (
<>
<GlobalStyles />
<S.Container>
<CreditCard
cardNumbers={cardNumbers}
month={month}
year={year}
name={name}
cardBrand={cardBrand}
/>
<S.CardInfoContainer>
<S.Wrapper>
<InputInfo
title={CARD_NUMBER.title}
description={CARD_NUMBER.description}
inputTitle={CARD_NUMBER.inputTitle}
>
{cardNumbers.map((cardNumber, index) => {
const uniqueId = 'cardNumbers' + index;
return (
<React.Fragment key={uniqueId}>
<Label htmlFor={uniqueId} />
<Input
id={uniqueId}
placeholder="1234"
type="text"
maxLength={MAX_LENGTH.CARD_NUMBERS}
value={cardNumber.value}
onChange={(e) => cardNumbersChangeHandler(e, index)}
isError={cardNumber.isError}
/>
</React.Fragment>
);
})}
</InputInfo>
<S.ErrorContainer>
<S.ErrorMessageSpan>
{cardNumbers.some((cardNumber) => cardNumber.isError) && CARD_NUMBER.errorMessage}
</S.ErrorMessageSpan>
</S.ErrorContainer>
</S.Wrapper>

<S.Wrapper>
<InputInfo
title={EXPIRATION_PERIOD.title}
description={EXPIRATION_PERIOD.description}
inputTitle={EXPIRATION_PERIOD.inputTitle}
>
<Label htmlFor={'month'} />
<Input
id={'month'}
placeholder={'MM'}
type="text"
value={month}
maxLength={MAX_LENGTH.MONTH}
onChange={monthChangeHandler}
isError={monthError}
/>
<Label htmlFor={'year'} />
<Input
id={'year'}
placeholder={'YY'}
type="text"
maxLength={MAX_LENGTH.YEAR}
value={year}
onChange={yearChangeHandler}
isError={yearError}
/>
</InputInfo>
<S.ErrorContainer>
<S.ErrorMessageSpan>
{monthError && yearError ? EXPIRATION_PERIOD.monthErrorMessage : ''}
{!monthError && yearError ? EXPIRATION_PERIOD.yearErrorMessage : ''}
{monthError && !yearError ? EXPIRATION_PERIOD.monthErrorMessage : ''}
</S.ErrorMessageSpan>
</S.ErrorContainer>
</S.Wrapper>

<S.Wrapper>
<InputInfo title={OWNER_NAME.title} inputTitle={OWNER_NAME.inputTitle}>
<Label htmlFor={'name'} />
<Input
id="name"
maxLength={MAX_LENGTH.NAME}
onChange={nameChangeHandler}
isError={nameError}
placeholder="JOHN DOE"
type="text"
value={name}
/>
</InputInfo>
<S.ErrorContainer>
<S.ErrorMessageSpan>{nameError && OWNER_NAME.errorMessage}</S.ErrorMessageSpan>
</S.ErrorContainer>
</S.Wrapper>
</S.CardInfoContainer>
</S.Container>
{children}
</>
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/GlobalStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import reset from 'styled-reset';
const GlobalStyles = createGlobalStyle`
${reset}

select option[value=""][disabled] {
display: none;
}

a{
text-decoration: none;
color: inherit;
Expand All @@ -14,10 +18,6 @@ const GlobalStyles = createGlobalStyle`
}

#root {
display: flex;
justify-content: center;
align-items: center;

width: 100vw;
height: auto;
min-height: 100vh;
Expand Down
22 changes: 21 additions & 1 deletion src/app.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ export const Container = styled.div`
padding: 20px 30px;
width: 376px;
height: 680px;
background-color: beige;
overflow: scroll;
position: relative;
`;

export const ContentCard = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
min-height: 100vh;
`;

export const CardInfoContainer = styled.div`
Expand All @@ -30,3 +40,13 @@ export const Wrapper = styled.div`
flex-direction: column;
gap: 8px;
`;

export const ButtonContainer = styled.div`
position: fixed;
bottom: 0px;
right: 0;
left: 0;
width: 376px;
margin: 0px auto;
height: 52px;
`;
Binary file added src/assets/images/complete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { InitialCardNumberState } from '../hooks/useCardNumber';
import MasterCardImage from '../assets/images/mastercard.png';
import VisaCardImage from '../assets/images/visa.png';
import { InitialCardNumberState } from '@/types';
import MasterCardImage from '../../assets/images/mastercard.png';
import VisaCardImage from '../../assets/images/visa.png';
import * as C from './index.style';
import * as S from './creditCard.style';

type CreditCardProps = {
Expand All @@ -9,24 +10,32 @@ type CreditCardProps = {
year: string;
name: string;
cardBrand: 'none' | 'Visa' | 'MasterCard';
backgroundColor: string;
};

const DATE_SEPARATOR = '/';

export default function CreditCard({ cardNumbers, month, year, name, cardBrand }: CreditCardProps) {
export default function CreditCard({
cardNumbers,
month,
year,
name,
cardBrand,
backgroundColor,
}: CreditCardProps) {
const cardBrandImageSrc =
cardBrand === 'MasterCard' ? MasterCardImage : cardBrand === 'Visa' ? VisaCardImage : '';

return (
<S.Container>
<S.CardContainer>
<C.Container>
<C.CardContainer $backgroundColor={backgroundColor} $padding={'8px 12px'}>
<S.CardHeader>
<S.CardHeaderContentWrapper>
<S.IcChip />
</S.CardHeaderContentWrapper>
<S.CardHeaderContentWrapper>
{cardBrandImageSrc ? (
<S.CardBrand src={cardBrandImageSrc} alt={'cardBrandImage'} />
<S.CardBrand src={cardBrandImageSrc} alt={`${cardBrand} 카드`} />
) : null}
</S.CardHeaderContentWrapper>
</S.CardHeader>
Expand All @@ -52,7 +61,7 @@ export default function CreditCard({ cardNumbers, month, year, name, cardBrand }
<S.Text>{month + `${month || year ? DATE_SEPARATOR : ''}` + year}</S.Text>
<S.Text>{name}</S.Text>
</S.CardInfoWrapper>
</S.CardContainer>
</S.Container>
</C.CardContainer>
</C.Container>
);
}
18 changes: 18 additions & 0 deletions src/components/cards/CvcCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as S from './cvcCard.style';
import * as C from './index.style';

type CvcCardProps = {
cvc: string;
};

export default function CvcCard({ cvc }: CvcCardProps) {
return (
<C.Container>
<C.CardContainer $backgroundColor={'rgba(213, 213, 213, 1)'}>
<S.CvcNumberWrapper $backgroundColor={'rgba(203, 186, 100, 1)'}>
<S.CvcNumberText>{cvc}</S.CvcNumberText>
</S.CvcNumberWrapper>
</C.CardContainer>
</C.Container>
);
}