Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a298bb9
docs: 기능 명세 작성
ha-kuku Apr 16, 2025
fc8f4eb
chore: styled component 설치
ha-kuku Apr 16, 2025
0fc0c15
feat: Input 컴포넌트 구현
ha-kuku Apr 16, 2025
2491d57
style: input 스타일 변경
ha-kuku Apr 16, 2025
38f0d57
feat: CardNumberInputs 구현
ha-kuku Apr 16, 2025
3c87d9e
feat: input width 동적으로 조정
ha-kuku Apr 16, 2025
6381154
feat:CardExpirationPeriodInputs 구현
ha-kuku Apr 16, 2025
dd03715
feat:CardCVCNumberInputs구현
ha-kuku Apr 16, 2025
5940f2d
feat: SectionTitle 구현
ha-kuku Apr 16, 2025
7bbf904
feat:CardNumberSection구현
ha-kuku Apr 16, 2025
859f0f0
feat:CardExpirationPeriodSection 구현
ha-kuku Apr 16, 2025
ce0c0b7
feat:CardCVCNumberSection 구현
ha-kuku Apr 16, 2025
3f77c59
feat:CardPreview UI 구현
ha-kuku Apr 16, 2025
89b35a7
feat: inputProps type 삭제
ha-kuku Apr 16, 2025
2f73a15
feat: 카드번호 동적 Ui로 표현
ha-kuku Apr 16, 2025
96b5102
feat: cardNumberInput 유효성 검사 추가
ha-kuku Apr 16, 2025
047115d
feat: cardExpirationPeriodInput 유효성 검사 추가
ha-kuku Apr 16, 2025
ac4a397
feat: cardExpirationPeriodInput UI 동적 표현
ha-kuku Apr 16, 2025
91a1e48
feat: cardCVCNumberInput UI 동적 표현
ha-kuku Apr 16, 2025
b2d57e6
feat: cardPreview UI 동적 표현
ha-kuku Apr 16, 2025
842e58a
fix: storybook 에러 수정
ha-kuku Apr 17, 2025
bea0cd5
fix: 경로 오류 수정
ha-kuku Apr 17, 2025
64f29a9
style: styled component 변수명 변경
ha-kuku Apr 17, 2025
c9d806a
refactor: app UI 중앙 정렬
ha-kuku Apr 17, 2025
efc7db3
refactor: 공통 type 분리
ha-kuku Apr 17, 2025
74da666
refactor: 상수화
ha-kuku Apr 17, 2025
2c60150
refactor: 공통 로직 분리
ha-kuku Apr 17, 2025
d55a629
style: 코드 스타일 변경
ha-kuku Apr 17, 2025
4eceaab
chore: chromatic workflow 추가
ha-kuku Apr 17, 2025
0544ece
docs: 기능 사항 체크
ha-kuku Apr 17, 2025
41f544b
feat: 페이지 배포
ha-kuku Apr 17, 2025
f79fc36
docs: 리팩터링 사항 추가
ha-kuku Apr 20, 2025
9ed0df5
refactor: 카드 로고 동적 UI 구현 방식 변경
ha-kuku Apr 20, 2025
dc1aca9
refactor: validation 조건 수정
ha-kuku Apr 20, 2025
a8a3ce2
style: types 폴더명 앞 공백 제거
ha-kuku Apr 20, 2025
e9fe607
fix: 문자 입력 시 오류메시지 미로딩 오류 해결
ha-kuku Apr 20, 2025
9b0b22d
refactor: 에러 처리 방식 변경
ha-kuku Apr 20, 2025
1548759
refactor: input 중복 코드 줄이기
ha-kuku Apr 20, 2025
0180a0f
style: 코드 포멧터 적용
ha-kuku Apr 20, 2025
3a58d86
docs: 리팩터리 반영사항 체크
ha-kuku Apr 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 36 additions & 36 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,48 @@
import "./App.css";
import styled from "styled-components";
import CardPreview from "./components/cardPreview/CardPreview";
import CardNumberSection from "./components/cardNumberSection/CardNumberSection";
import CardExpirationPeriodSection from "./components/cardExpirationPeriodSection/CardExpirationPeriodSection";
import CardCVCNumberSection from "./components/cardCVCNumberSection/CardCVCNumberSection";
import { useState } from "react";
import { ExpirationPeriod, Position } from "./\btypes/index.types";
import { INITIALIZE_VALUE } from "./constants/constant";
import './App.css';
import styled from 'styled-components';
import CardPreview from './components/cardPreview/CardPreview';
import CardNumberSection from './components/cardNumberSection/CardNumberSection';
import CardExpirationPeriodSection from './components/cardExpirationPeriodSection/CardExpirationPeriodSection';
import CardCVCNumberSection from './components/cardCVCNumberSection/CardCVCNumberSection';
import { useState } from 'react';
import { ExpirationPeriod, Position } from './types/index.types';
import { INITIALIZE_VALUE } from './constants/constant';

const StyledApp = styled.div`
display: flex;
justify-content : center;
`

display: flex;
justify-content: center;
`;

const StyledFrame = styled.div`
display: inline-flex;
padding: 77px 30px 19px 31px;
flex-direction: column;
justify-content: flex-end;
align-items : center;
gap: 45px;
background-color: white;
width: 100%;
max-width: 600px;
box-sizing: border-box;
`

display: inline-flex;
padding: 77px 30px 19px 31px;
flex-direction: column;
justify-content: flex-end;
align-items: center;
gap: 45px;
background-color: white;
width: 100%;
max-width: 600px;
box-sizing: border-box;
`;

type CardNumberState = {
[key in Position]: string;
};

type ExpirationPeriodState = {
[key in keyof ExpirationPeriod]: string;
}

};

function App() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화살표 함수 대신 함수 선언문을 사용하신 이유가 궁금합니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 이유를 가지고 선언문을 사용하지는 않았습니다..

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수 선언문과 화살표 함수의 차이를 찾아보시고 의도에 맞는 코드를 작성하실 수 있으면 좋을거 같네요 ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네! 구분해서 사용하겠습니다!

const [cardNumber, setCardNumber] = useState<CardNumberState>({
first: INITIALIZE_VALUE,
second: INITIALIZE_VALUE,
third: INITIALIZE_VALUE,
fourth : INITIALIZE_VALUE,
fourth: INITIALIZE_VALUE,
});

const [expirationPeriod, setExpirationPeriod] = useState<ExpirationPeriodState >({
const [expirationPeriod, setExpirationPeriod] = useState<ExpirationPeriodState>({
month: INITIALIZE_VALUE,
year: INITIALIZE_VALUE,
});
Expand All @@ -55,27 +52,30 @@ function App() {
function changeCardNumber(position: Position, cardNumber: string) {
setCardNumber((prev) => {
prev[position] = cardNumber;
return {...prev}
})
return { ...prev };
});
}

function changeExpirationPeriod(expirationPeriod: keyof ExpirationPeriod, date: string) {
setExpirationPeriod((prev) => {
prev[expirationPeriod] = date;
return {...prev}
})
return { ...prev };
});
}

function changeCVCNumber(CVCNumber : string ) {
function changeCVCNumber(CVCNumber: string) {
setCVCNumber(CVCNumber);
}

return (
<StyledApp>
<StyledFrame>
<CardPreview cardNumber={cardNumber} expirationPeriod={expirationPeriod} />
<CardPreview cardNumber={cardNumber} expirationPeriod={expirationPeriod} />
<CardNumberSection cardNumber={cardNumber} changeCardNumber={changeCardNumber} />
<CardExpirationPeriodSection expirationPeriod={expirationPeriod} changeExpirationPeriod={changeExpirationPeriod}/>
<CardExpirationPeriodSection
expirationPeriod={expirationPeriod}
changeExpirationPeriod={changeExpirationPeriod}
/>
<CardCVCNumberSection CVCNumber={CVCNumber} changeCVCNumber={changeCVCNumber} />
</StyledFrame>
</StyledApp>
Expand Down
91 changes: 45 additions & 46 deletions src/components/cardCVCNumberInputs/CardCVCNumberInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,57 @@
import Input from "../input/Input"
import { CardCVCNumberSectionProps } from "../../\btypes/index.types"
import { useState } from "react"
import { isValidLength, isValidNumber } from "../../util/validation"
import { NO_ERROR } from "../../constants/constant"
import { StyledContainer, StyledInputWrap, StyledErrorMessage } from "../../styled-component/inputs"
import Input from '../input/Input';
import { CardCVCNumberSectionProps } from '../../types/index.types';
import { useState } from 'react';
import { isValidLength, isValidNumber } from '../../util/validation';
import { NO_ERROR } from '../../constants/constant';
import { StyledContainer, StyledInputWrap, StyledErrorMessage } from '../../styled-component/inputs';

const CVC_NUMBER_LENGTH = 3;

const errorMessage = {
length: '3자리만 입력 가능합니다.',
number : '숫자만 입력 가능합니다.'
}
length: '3자리만 입력 가능합니다.',
number: '숫자만 입력 가능합니다.',
};

function CardCVCNumberInputs({ CVCNumber, changeCVCNumber }: CardCVCNumberSectionProps) {
const [error, setError] = useState(NO_ERROR);

const [error, setError] = useState(NO_ERROR);

function checkValidation(length : number, CVCNumber: string) {
if (CVCNumber === NO_ERROR) {
setError(NO_ERROR)
return;
}

if (!isValidLength(CVCNumber, length)) {
setError(errorMessage.length)
return;
}
if (!isValidNumber(CVCNumber)) {
setError(errorMessage.number)
return;
}

setError(NO_ERROR)
function checkValidation(length: number, CVCNumber: string) {
if (CVCNumber === NO_ERROR) {
setError(NO_ERROR);
return;
}

if (!isValidLength(CVCNumber, length)) {
setError(errorMessage.length);
return;
}
if (!isValidNumber(CVCNumber)) {
setError(errorMessage.number);
return;
}

return (
<StyledContainer>
<label htmlFor="">CVC</label>
<StyledInputWrap>
<Input
value={CVCNumber}
onChange={(e) => {
changeCVCNumber(e.target.value);
checkValidation(CVC_NUMBER_LENGTH, e.target.value);
}}
isError={error !== NO_ERROR}
width='100%'
maxLength={CVC_NUMBER_LENGTH}
placeholder='123' />
</StyledInputWrap>
{error !== NO_ERROR ? <StyledErrorMessage>{error}</StyledErrorMessage> : null}
</StyledContainer>
)
setError(NO_ERROR);
}

return (
<StyledContainer>
<label htmlFor="">CVC</label>
<StyledInputWrap>
<Input
value={CVCNumber}
onChange={(e) => {
changeCVCNumber(e.target.value);
checkValidation(CVC_NUMBER_LENGTH, e.target.value);
}}
isError={error !== NO_ERROR}
width="100%"
maxLength={CVC_NUMBER_LENGTH}
placeholder="123"
/>
</StyledInputWrap>
{error !== NO_ERROR ? <StyledErrorMessage>{error}</StyledErrorMessage> : null}
</StyledContainer>
);
}

export default CardCVCNumberInputs
export default CardCVCNumberInputs;
38 changes: 19 additions & 19 deletions src/components/cardCVCNumberSection/CardCVCNumberSection.tsx
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 모든 컴포넌트가 같은 레벨에 위치하고 있는데요. 애플리케이션 전체에 걸쳐 재사용 되는 함수와 특정 용도로 사용되는 함수를 폴더 구조를 통해 나눠보시면 좋을거 같네요

Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import SectionTitle from "../sectionTitle/SectionTitle"
import CardCVCNumberInputs from "../cardCVCNumberInputs/CardCVCNumberInputs"
import styled from "styled-components"
import { CardCVCNumberSectionProps } from "../../\btypes/index.types"
import SectionTitle from '../sectionTitle/SectionTitle';
import CardCVCNumberInputs from '../cardCVCNumberInputs/CardCVCNumberInputs';
import styled from 'styled-components';
import { CardCVCNumberSectionProps } from '../../types/index.types';

const StyledContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 16px;
width: 100%;
`
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: 16px;
width: 100%;
`;

function CardCVCNumberSection({ CVCNumber, changeCVCNumber } : CardCVCNumberSectionProps) {
return (
<StyledContainer>
<SectionTitle title='CVC 번호를 입력해 주세요'/>
<CardCVCNumberInputs CVCNumber={CVCNumber} changeCVCNumber={changeCVCNumber}/>
</StyledContainer>
)
function CardCVCNumberSection({ CVCNumber, changeCVCNumber }: CardCVCNumberSectionProps) {
return (
<StyledContainer>
<SectionTitle title="CVC 번호를 입력해 주세요" />
<CardCVCNumberInputs CVCNumber={CVCNumber} changeCVCNumber={changeCVCNumber} />
</StyledContainer>
);
}

export default CardCVCNumberSection
export default CardCVCNumberSection;
Loading