이번 시간에는 결제창 기능을 작업하겠습니다
먼저는 이 '결제창으로 이동'(Go to Checkout) 버튼을
장바구니에 항목이 있을 때만 띄우겠습니다
즉, 장바구니가 비어 있다면 이 버튼은 보이지 않습니다
이 작업은 상대적으로 간단한 편입니다
먼저 장바구니 컴포넌트 파일로 가서
이 버튼을 조건부로 보여주도록 하면 됩니다
즉, 장바구니에 항목이 있는지 여부가 조건이 됩니다
여기에서 장바구니 컨텍스트를 사용해서
항목 배열이 비었는지 확인하면 됩니다
만약 이 길이가 0보다 크다면
장바구니에 항목이 있다는 뜻이 됩니다
이 경우에는 버튼을 띄워야 합니다
반면에 아무 것도 띄우지 않을 때는 null을 반환합니다
물론 이 코드를 더 간결하게 작성하려면
일단 else를 지우고 이 작업을 하면 됩니다
바로 논리적 AND 연산자를 사용하면 됩니다
이렇게 하면 이 버튼을 띄우는 경우는
장바구니에 항목이 있을 경우 뿐입니다
이제 확인해 보면 장바구니가 비었으므로
'닫기' 버튼만 존재합니다
반면 만약에 장바구니에 항목이 있다면
이렇게 더하고 보면 '결제창으로 이동' 버튼이 생겼습니다
여기에서 다음 단계는 바로
이 버튼을 누르면 결제 컴포넌트로 넘어가게 하는 것입니다
그러므로 해당 컴포넌트를 더하겠습니다
결제(Checkout) 컴포넌트 파일을 컴포넌트 폴더에 만듭니다
이 모든 내용은 소스 폴더(src)에 있습니다
여기에서는 결제 컴포넌트 항목을 내보내고
먼저는 JSX 코드를 반환해야 합니다
JSX 코드가 화면에 보이는 시점은
이 결제 컴포넌트가 화면에 뜰 때입니다
제 계획은 이 결제 컴포넌트를 띄울 때
모달로 띄우는 것입니다
그러므로 장바구니 모달과 비슷하게
커스텀 모달 컴포넌트를 사용하겠습니다
모달 파일(Modal.jsx)에서 가져오겠습니다
여기에는 양식(form)을 하나 만들어서
사용자가 입력해야 하는 칸을 여러 개 만들어서
입력 후에 결제 요청을 하도록 하겠습니다
이 양식 안에는
'결제' (Checkout)라는 제목을 만들고
제목 아래에는 p태그를 만들어서
지불해야 하는 최종 금액을 보여주겠습니다
이렇게 하면 사용자가 얼마를 내야 하는지 볼 수 있습니다
물론 이 최종 금액은 장바구니 항목에 따라 달라집니다
그러므로 이 결제 컴포넌트가 이전에 작업해 두었던
장바구니 컨텍스트에 접근해야 합니다
이 장바구니 컨텍스트를 가져온 후에
useContext 훅(hook)을 사용하겠습니다
이제 여기에 장바구니 컨텍스트 객체가 생겼습니다
이 내용을 사용해서 총 결제 금액이 얼마인지
계산할 수 있습니다
이 계산을 위한 로직은 이미 만들어 두었습니다
바로 카트 파일(Cart.jsx)에 그 내용이 있으므로
이 내용을 공유된 파일로 보낼 수도 있지만
여기서는 그냥 복사 후 붙여넣겠습니다
결제 파일(Checkout.jsx)에 내용을 붙여넣고
여기 있는 총 금액을 내보낼 수 있습니다
물론 형식이 갖추어진 총 금액입니다
그러므로 만들어 둔 currencyFormatter를 가져오겠습니다
위치는 형식 파일(formatting.js)입니다
이제 이 내용을 아래쪽에서 사용해서
형식을 갖춘 장바구니 총액을 내보낼 수 있습니다
이 양식에는 여러 입력 창이 필요합니다
이 작업을 위해 공통 입력 컴포넌트를 만들겠습니다
위치는 UI 폴더입니다
물론 이 작업을 하지 않으셔도 됩니다
하지만 이렇게 하는 것이 편리한 이유는
양식 안에 여러 가지 입력을 받을 때
이 모든 입력은 구조가 동일하기 때문입니다
그러므로 공통 입력 컴포넌트를 만들어서
각 입력값마다 문단을 하나 반환하겠습니다
이것이 반환하는 내용입니다
className은 control로 설정해서 스타일을 위해 사용하겠습니다
그리고 라벨과 입력 요소를 아래에 만들겠습니다
물론 이 라벨은 외부에서도 구성이 가능해야 하므로
이 입력 컴포넌트는 속성을 사용해야 합니다
예를 들면 라벨 속성이 있습니다
혹은 ID 속성도 있습니다
이 라벨에서 htmlFor 속성을 id에 설정해서
브라우저가 라벨이 어떤 입력값과 연결되어 있는지 알 수 있습니다
일단은 ID 속성을 입력값에 설정해야 합니다
이렇게 하면 아까 말한대로 연결이 됩니다
그리고 htmlFor은 위의 className과 유사합니다
for은 이름으로 사용될 수 없는데
자바스크립트에서 for은 이미 용어가 있기 때문입니다
그러므로 이 속성 이름은 htmlFor로 하겠습니다
이제 라벨 태그 사이에
라벨 속성에서 받는 입력값을 반환할 수 있습니다
아래쪽 입력값에는
name이라는 속성을 설정해서
ID와 같은 값으로 설정하겠습니다
이렇게 하면 ID로 받는 값을 이름의 값으로 재사용할 수 있습니다
그리고 여기 이름 속성을 쓰는 이유는
이렇게 함으로써 나중에 양식을 제출할 때
이 입력값을 사용할 수 있기 때문입니다
이 때에는 브라우저에서 제공하는 양식 특성의
도움을 받을 것입니다
이렇게 함으로써 양식 제출이 훨씬 쉬워집니다
이것은 강의에서 양식을 다룰 때 이미 언급했습니다
물론 실습 목적으로
양식 값과 양식 유효성 검사를
원하는 대로 제어할 수도 있습니다
이 강의에서 양식을 다루었을 때
여러 가지 접근방식을 다루었기 때문입니다
이 데모 앱에서 저는 브라우저의 도움을
조금 받겠습니다
그러므로 required라는 내장 속성을 사용해서
이 입력값을 비운 상태로는 진행하지 못하게 하겠습니다
이제 이 내용을 입력 컴포넌트에서 작업하는 이유는
여기에서 빌드하고 있는 이 앱에서는
어차피 모든 입력값이 필수이기 때문입니다
다른 방식으로는
여기에 설정된 모든 다른 속성을 받아올 수 있습니다
그러면 이 속성을 입력에 펼쳐서
이 입력값을 밖에서도 구성할 수 있도록 설정할 수 있습니다
이 부분은 어떤 방식으로 작업하든지 실행을 하겠습니다
만약 이 유형을 외부에서도 구성 가능하게 만든다면
이 입력을 유연하게 사용해서
무엇이든 원하는 속성을
이 입력에 추가하게 됩니다
그러므로 이 작업은 꼭 하겠습니다
물론 required는 여전히 직접 작성하겠습니다
그 이유는 이 앱에서는 모든 입력값이 필수이기 때문입니다
이 정도면 많이 완성이 되었습니다
이제 재사용 가능한 입력 컴포넌트가 생겼습니다
이제 다시 결제 컴포넌트로 돌아가서
이 컴포넌트를 사용해 보겠습니다
가장 먼저는 컴포넌트를 가져와야 합니다
이렇게 가져온 후에는
아까 만든 입력 컴포넌트를 사용할 수 있습니다
예를 들어 Full Name 입력값을 받아서
사용자의 이름을 받도록 하겠습니다
이제 이 입력 유형을 문자로 해야 하는데
다행히 여기에서 모든 속성을 모아서
입력 요소에 펼쳐 두었기 때문에
이 형식이 그대로 전달되어
이 입력값에도 적용됩니다
그러므로 이 입력 내용은 모두 텍스트입니다
ID로는 full-name을 설정하겠습니다
이제 첫 단계가 끝났습니다
결제창의 두 번째 입력값 작업을 하겠습니다
라벨은 Email Address로 해서
사용자가 이메일 주소를 입력하게 하겠습니다
ID 또한 이메일로 하고
유형 또한 이메일이 되겠습니다
그리고 위쪽과 동일한 순서로 작성한 것은
크게 중요한 부분은 아니지만
작업의 일관성은 언제나 중요합니다
그러므로 다음 입력값은
사용자의 도로 주소를 받아야 합니다
이름은 Street로 하고 유형은 텍스트
ID는 street으로 하겠습니다
그 다음에는 입력값 두 개를 나란히 배치하겠습니다
인텍스 파일(index.CSS) 파일에서 이것을 위한 CSS 클래스를 제공합니다
여기에서는 div를 추가해서 클래스는 control-row로 하겠습니다
이 다음에 입력값을 두 개 더하면 됩니다
첫 번째 입력값은 우편번호를 받고
유형은 텍스트, ID는 postal-code가 됩니다
두 번째 입력값은 도시 이름을 받겠습니다
유형은 텍스트, ID는 city입니다
입력값 내용은 이 정도로 하겠습니다
이것으로 결제창 양식 내용을 작성했습니다
이제 여기에 버튼을 하나 추가해서
양식을 제출하도록 하겠습니다
먼저 양식 마지막에 문단을 하나 더해서
className은 modal-actions로 하겠습니다
그 다음에 버튼을 두 개 만들겠습니다
커스텀 버튼 컴포넌트를 사용할 텐데
물론 사용하기 위해서는 먼저 가져와야 합니다
첫 번째 버튼은 텍스트만 띄우는 버튼 형식으로
모달 창을 닫을 수 있게 하겠습니다
이제 두 번째 버튼의 경우에는
결제 양식을 제출할 수 있게 하겠습니다
이 버튼은 문자만 있는 형태가 아니라
표준 형식의 버튼입니다
양식 제출을 위한 로직 작성은
조금 있다가 하겠습니다
지금은 이 '닫기' 버튼이 창을 닫도록 하겠습니다
이 작업을 위해서는 이 버튼에
버튼이라는 유형을 지정하겠습니다
이 버튼이 양식 안에 있으므로 이 작업을 하지 않으면
이 버튼으로 양식을 제출해 버립니다
그러므로 버튼 유형을 설정해서
이 버튼으로 양식을 제출하는 대신에
모달 창을 닫도록 하겠습니다
그리고 모달 창을 닫기 위해서는
아까 추가했던 이 컨텍스트를 사용해야 합니다
즉 이 UserProgressContext입니다
UserProgressCtx를 사용해서 open이라는 속성을 모달에 사용해
대응을 시키겠습니다
바로 UserProgressCtx.progress과 checkout입니다
즉 open이 true인 경우는 이 컨텍스트의
progress가 결제(checkout)과 동일할 때입니다
이제 닫기 버튼을 눌러서 창을 닫을 수 있도록
함수를 하나 추가하는데, 이름은 handleClose로 하겠습니다
여기에서는 userProgressCtx.hideCheckout을 호출합니다
이제 이 함수와 이쪽의 버튼을 연결하겠습니다
onClick 속성을 추가하면 연결이 됩니다
이제 결제창 내용을 보기 위해서는
당연하지만 창이 열릴 수 있도록 해야 합니다
즉 장바구니에서 이 창을 열 수 있어야 합니다
여기 있는 '결제창으로 이동' 버튼을 사용해야 하므로
장바구니를 닫는 대신에
이 버튼은 결제창으로 이동을 시켜 주어야 하빈다
그러므로 여기에 함수를 하나 더하겠습니다
위치는 카트 컴포넌트고, 이름은 handleGoToCheckout입니다
이 정도면 적절한 이름 같습니다
이 함수에서는 userProgressCtx를 사용해서
showCheckout을 호출합니다
이 컨텍스트 안에서 이 함수가
userProgress를 checkout으로 설정해 주므로
이 progress는 이제 장바구니가 아닌 결제창에 들어가게 됩니다
그리고 장바구니 컴포넌트의 모달은 더이상
true를 open에 대한 값으로 받지 않습니다
그 이유는 open이 true일 수 있는 경우는
progress가 cart와 대응할 때 뿐인데
이제는 이런 경우가 생기지 않기 때문입니다
그러므로 이 모달은 자동으로 닫히고
보이지 않게 됩니다
대신에 이제는 결제창 모달이 뜹니다
이제 handleGoCheckout이
이 버튼과 연결되어 있어야 하는데
'결제창으로 이동' 버튼입니다
이제 앱을 확인해 보겠습니다
새로고침을 하고 항목을 몇 개 담은 후에
결제창으로 이동해 보면 장바구니가 닫힙니다
하지만 결제창 모달이 제대로 뜨지는 않았습니다
이 이유는 앱에 아직 결제 컴포넌트를
제대로 추가하지 않았기 때문입니다
이 작업을 먼저 하겠습니다
이 컴포넌트 트리에 항목을 더하겠습니다
위치는 장바구니 아래로 하겠습니다
그리고 물론 위쪽에서 먼저 컴포넌트를 가져와야 합니다
이제 이대로 저장하고 새로고침을 한 뒤
장바구니에 항목을 담아서 '결제창으로 이동'을 누르면
이제 모달 창이 하나 뜨고 아까 만든 양식이 나옵니다
이제 남은 작업은 양식을 제출하도록 하는 것입니다
하지만 그 전에 버그를 하나 수정하겠습니다
모달과 관련된 버그입니다
이 대화상자를 닫을 때에 버튼을 누르지 않고
Esc 버튼을 눌러도 닫히는데
이 경우에는 이 모달을 다시 열 수 없습니다
이 버튼을 아무리 눌러도 창이 뜨지 않습니다
그 이유는 모달을 닫을 때에 버튼 대신에
Esc 키를 사용하면
브라우저가 이 대화상자를 닫게 되는데
이 때 앱 내부에서 userProgress는 변하지 않습니다
그러므로 UserProgress는 장바구니에 계속
머물러 있게 됩니다
즉 이 의미는 이 앱이
아직 대화상자가 열렸다고 생각하는 것입니다
open 속성값이 변하지 않았고
그대로이기 때문입니다
그러므로 userProgress 상태를 업데이트해야 하는데
업데이트하는 시점은 Esc 버튼을 눌러서 창을 닫을 때입니다
이 작업을 위해 모달 컴포넌트로 돌아가겠습니다
여기 있는 대화상자 요소에서
onClose 속성을 더하겠습니다
창을 닫는 이벤트를 브라우저가 실행하는 경우가
우리가 만든 버튼이 아니라
Esc 키 등을 사용하는 때이기 때문입니다
그러므로 이 내용을 이 위쪽의
모달 컴포넌트가 사용되는 곳으로 전달하겠습니다
여기에서 속성을 하나 받겠습니다
이름은 동일한 onClose입니다
이 속성을 내장된 onClose 속성의 값으로 받겠습니다
이 속성은 내장된 대화상자 요소에 있습니다
이제 모달 컴포넌트를 사용하는 위치라면
예를 들어 장바구니에서
onClose 속성을 더해서 함수를 실행할 수 있습니다
즉 이 함수는 모달이 닫힐 때 실행됩니다
Esc 키 등으로 창을 닫아도 여전히 실행하는 것입니다
장바구니 컴포넌트 안에서는
이 handleCloseCart 함수를 실행하게 됩니다
이미 작성한 내용입니다
이렇게 하면 컨텍스트가 담당하는 progress 상태와
브라우저가 제어하는 창의 가시성에 대한
싱크를 맞출 수 있게 됩니다
동일한 작업을 결제 컴포넌트에서도 하겠습니다
모달에 onClose 속성을 추가하고
창을 닫을 때마다 handleClose를 실행하겠습니다
이대로 저장 후 새로고침을 해 보면
장바구니를 열고 Esc 버튼을 눌러서 닫아도
다시 열 수가 있습니다
하지만 문제점이 있습니다
버튼을 눌러 결제창으로 이동해 보겠습니다
이제 이 버튼이 작동하지 않고
아예 창을 닫아버립니다
이 현상의 이유는
progrss 값이 cart에서 checkout으로 변하기 위해서는
버튼을 클릭해야 하는데, 이 말인즉슨
이제 장바구니 컴포넌트의 open이 false이므로
이 모달이 닫히는 것입니다
물론 이 창을 닫는 것은
브라우저가 하는 일이므로 progress 상태를 리셋하여
장바구니 창을 보이지 않게 하는 것입니다
이 부분을 수정할 수 있는 방법이 몇 가지 있습니다
가장 쉬운 해결방식은
모달 컴포넌트의 onClick 속성으로 가 보겠습니다
위치는 장바구니 컴포넌트 파일입니다
여기에서 handleCloseCart를 언제나 실행하고 있는데
여기에서 문제가 일어나므로
현재 userProgress가 cart와 동일한지를 확인해야 합니다
만약 같다면 handleCloseCart를 사용해서
이 이벤트를 제어하는 함수로 쓰겠습니다
만약 동일하지 않다면 아무 것도 하지 않으므로 널을
이 이벤트를 제어하는 함수로 쓰겠습니다
이렇게 되면 결제창으로 넘어갈 때에
이 함수를 재실행함으로써 창을 닫는 것이 아니라
아무 작업도 하지 않게 됩니다
이대로 저장하고 새로고침을 해 보겠습니다
이제 다시 결제창으로 넘어갈 수 있습니다
그리고 Esc 키를 눌러서 장바구니를 닫고 난 후에도
정상적으로 결제창에 접근할 수 있습니다
이제 의도한 대로 작동이 잘 되고 있습니다
이 창을 띄우기 전 어떤 작업을 했든지간에
결제창으로 무사히 넘어갈 수가 있습니다
이렇게 문제를 해결했습니다