-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] 현상관리 페이지 2차 QA 반영 #256
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
Changes from all commits
91df3b2
e721430
c370e05
babe8e8
f6b9346
78b2b63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| import type React from "react"; | ||
| import { ActionButton, InputForm } from "@/components/auth"; | ||
| import { CTA_Button } from "@/components/common"; | ||
| import { DaumAddressSearch } from "@/components/photoManage/DaumAddressSearch"; | ||
| import { useCreateAddress } from "@/hooks/member"; | ||
| import { usePrintOrderStore } from "@/store/usePrintOrder.store"; | ||
| import { useEffect, useMemo, useState } from "react"; | ||
| import { useNavigate } from "react-router"; | ||
|
|
||
| export function AddressDetailPage() { | ||
| const navigate = useNavigate(); | ||
|
|
||
| const deliveryAddress = usePrintOrderStore((s) => s.deliveryAddress); | ||
| const setDeliveryAddress = usePrintOrderStore((s) => s.setDeliveryAddress); | ||
|
|
||
| const { mutate: addAddress, isPending } = useCreateAddress(); | ||
|
|
||
| //주소 재검색 모달 | ||
| const [isSearchOpen, setIsSearchOpen] = useState(false); | ||
|
|
||
| //입력값: 배송지명 / 상세주소 | ||
| const [addressName, setAddressName] = useState(""); | ||
| const [addressDetail, setAddressDetail] = useState( | ||
| deliveryAddress?.addressDetail ?? "", | ||
| ); | ||
|
|
||
| //store(주소선택)에서 온 값이 바뀌면 상세주소는 유지 | ||
| useEffect(() => { | ||
| const id = window.setTimeout(() => { | ||
| setAddressDetail(deliveryAddress?.addressDetail ?? ""); | ||
| }, 0); | ||
|
|
||
| return () => window.clearTimeout(id); | ||
| }, [deliveryAddress?.addressDetail]); | ||
|
|
||
| const zipcode = deliveryAddress?.zipcode ?? ""; | ||
| const address = deliveryAddress?.address ?? ""; | ||
|
|
||
| const canSubmit = useMemo(() => { | ||
| const nameOk = addressName.trim().length > 0; | ||
| const detailOk = addressDetail.trim().length > 0; | ||
| const baseOk = zipcode.trim().length > 0 && address.trim().length > 0; | ||
| return nameOk && detailOk && baseOk && !isPending; | ||
| }, [addressName, addressDetail, zipcode, address, isPending]); | ||
|
|
||
| const handleAddressFound = (data: { zipcode: string; address: string }) => { | ||
| //주소 다시 선택하면 store 갱신 | ||
| setDeliveryAddress({ | ||
| recipientName: "", // store 타입 유지용(지금 플로우에선 안 씀) | ||
| phone: "", // store 타입 유지용(지금 플로우에선 안 씀) | ||
| zipcode: data.zipcode, | ||
| address: data.address, | ||
| addressDetail, // 현재 입력중 상세주소 유지 | ||
| }); | ||
|
Comment on lines
+48
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
// In src/types/photomanage/printOrder.ts
export interface DeliveryAddressRequest {
recipientName?: string;
phone?: string;
zipcode: string;
address: string;
addressDetail?: string;
} |
||
|
|
||
| setIsSearchOpen(false); | ||
| }; | ||
|
|
||
| const handleSubmit = () => { | ||
| if (!canSubmit) return; | ||
|
|
||
| // 기본주소는 zustand에서 가져온 그대로 사용 | ||
| addAddress( | ||
| { | ||
| addressName: addressName.trim(), | ||
| zipcode, | ||
| address, | ||
| addressDetail: addressDetail.trim(), | ||
| isDefault: false, | ||
| }, | ||
| { | ||
| onSuccess: (res) => { | ||
| const newId = res.data.addressId; | ||
|
|
||
| // SelectAddressPage로 돌아가면서 상세 주소 정보 입력한 주소가 선택되게 | ||
| navigate("../select-address", { | ||
| replace: true, | ||
| state: { selectedAddressId: newId }, | ||
| }); | ||
| }, | ||
| }, | ||
| ); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex h-full flex-col"> | ||
| <main className="flex flex-1 flex-col gap-10 py-10"> | ||
| <section className="flex gap-[1.25rem]"> | ||
| <InputForm | ||
| name="주소" | ||
| placeholder="주소를 선택해주세요" | ||
| size="medium" | ||
| value={address} | ||
| onChange={() => {}} | ||
| disabled={true} | ||
| /> | ||
| <ActionButton | ||
| type="button" | ||
| text="주소 찾기" | ||
| disabled={false} | ||
| onClick={() => setIsSearchOpen(true)} | ||
| /> | ||
| </section> | ||
|
|
||
| <InputForm | ||
| name="상세주소" | ||
| placeholder="상세주소를 입력해주세요" | ||
| size="large" | ||
| value={addressDetail} | ||
| onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
| setAddressDetail(e.target.value) | ||
| } | ||
| /> | ||
|
|
||
| <InputForm | ||
| name="배송지명" | ||
| placeholder="배송지명을 입력해주세요 (예: 우리집, 회사)" | ||
| size="large" | ||
| value={addressName} | ||
| onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
| setAddressName(e.target.value) | ||
| } | ||
| /> | ||
| </main> | ||
|
|
||
| <footer className="border-neutral-850 sticky bottom-0 z-50 h-[var(--tabbar-height)] w-full max-w-6xl border-t bg-neutral-900"> | ||
| <div className="flex h-full items-center"> | ||
| <CTA_Button | ||
| text={isPending ? "저장 중..." : "입력 완료"} | ||
| size="xlarge" | ||
| color={canSubmit ? "orange" : "black"} | ||
| disabled={!canSubmit} | ||
| onClick={handleSubmit} | ||
| /> | ||
| </div> | ||
| </footer> | ||
|
|
||
| <DaumAddressSearch | ||
| open={isSearchOpen} | ||
| onClose={() => setIsSearchOpen(false)} | ||
| onComplete={handleAddressFound} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,54 @@ | ||
| import type React from "react"; | ||
| import { InputForm } from "@/components/auth"; | ||
| import { CTA_Button } from "@/components/common"; | ||
| import { usePrintOrderStore } from "@/store/usePrintOrder.store"; | ||
| import { useMemo, useState } from "react"; | ||
| import { useEffect, useMemo, useState } from "react"; | ||
| import { useNavigate } from "react-router"; | ||
|
|
||
| export function DetailInfoPage() { | ||
| const navigate = useNavigate(); | ||
|
|
||
| const deliveryAddress = usePrintOrderStore((s) => s.deliveryAddress); | ||
| const setDeliveryAddress = usePrintOrderStore((s) => s.setDeliveryAddress); | ||
| const setSelectedOptions = usePrintOrderStore((s) => s.setSelectedOptions); | ||
|
|
||
| //store 값으로 초기화 | ||
| const [recipientName, setRecipientName] = useState( | ||
| deliveryAddress?.recipientName ?? "", | ||
| ); | ||
| const [phone, setPhone] = useState(deliveryAddress?.phone ?? ""); | ||
|
|
||
| const [recipientName, setRecipientName] = useState(""); | ||
| const [phone, setPhone] = useState(""); | ||
| //뒤로 갔다가 다시 들어왔을 때(또는 주소 선택 페이지에서 address가 갱신됐을 때) | ||
| //로컬 state가 store를 따라가게 동기화 | ||
| useEffect(() => { | ||
| const id = window.setTimeout(() => { | ||
| setRecipientName(deliveryAddress?.recipientName ?? ""); | ||
| setPhone(deliveryAddress?.phone ?? ""); | ||
| }, 0); | ||
|
|
||
| return () => window.clearTimeout(id); | ||
| }, [deliveryAddress?.recipientName, deliveryAddress?.phone]); | ||
|
Comment on lines
+23
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const digits = e.target.value.replace(/\D/g, "").slice(0, 11); | ||
| setPhone(digits); | ||
|
|
||
| if (!deliveryAddress) return; | ||
| setDeliveryAddress({ | ||
| ...deliveryAddress, | ||
| phone: digits, | ||
| }); | ||
| }; | ||
|
|
||
| const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const next = e.target.value; | ||
| setRecipientName(next); | ||
|
|
||
| if (!deliveryAddress) return; | ||
| setDeliveryAddress({ | ||
| ...deliveryAddress, | ||
| recipientName: next.trimStart(), //앞 공백만 제거 | ||
| }); | ||
| }; | ||
|
|
||
| const isNextEnabled = useMemo(() => { | ||
|
|
@@ -31,6 +65,9 @@ export function DetailInfoPage() { | |
| recipientName: recipientName.trim(), | ||
| phone, | ||
| }); | ||
|
|
||
| setSelectedOptions({}); | ||
|
|
||
| navigate("/photoManage/print-option"); | ||
| }; | ||
|
|
||
|
|
@@ -42,7 +79,7 @@ export function DetailInfoPage() { | |
| placeholder="받는 사람의 이름을 입력해 주세요" | ||
| size="large" | ||
| value={recipientName} | ||
| onChange={(e) => setRecipientName(e.target.value)} | ||
| onChange={handleNameChange} | ||
| /> | ||
| <InputForm | ||
| name="휴대폰 번호" | ||
|
|
@@ -52,6 +89,7 @@ export function DetailInfoPage() { | |
| onChange={handlePhoneChange} | ||
| /> | ||
| </main> | ||
|
|
||
| <footer className="border-neutral-850 sticky bottom-0 z-50 h-[var(--tabbar-height)] w-full max-w-6xl border-t bg-neutral-900"> | ||
| <div className="flex h-full items-center"> | ||
| <CTA_Button | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기서
window.setTimeout(..., 0)을 사용하는 것은 불필요해 보입니다.useEffect는 렌더링 후에 실행되며, React가 상태 업데이트를 배치(batch) 처리합니다.setTimeout을 사용하면 컴포넌트의 동작을 예측하기 어렵게 만들고 디버깅을 복잡하게 할 수 있습니다.useEffect내에서 직접setAddressDetail을 호출하는 것이 더 명확합니다.